home *** CD-ROM | disk | FTP | other *** search
/ HPAVC / HPAVC CD-ROM.iso / WINER.ZIP / CHAP12.TXT < prev    next >
Text File  |  1994-09-04  |  201KB  |  4,258 lines

  1.                                CHAPTER 12
  2.  
  3.                       ASSEMBLY LANGUAGE PROGRAMMING
  4.  
  5.  
  6. This book has consistently presented programming techniques that reduce the
  7. size of your programs, and make them run faster.  Most of the discussions
  8. focused on ways to write efficient BASIC code, and several showed how to
  9. access system interrupt services.  Where speed was critical or BASIC was
  10. inflexible, I presented subroutines written in assembly language.
  11.    Assembly language is the most powerful way to communicate with a PC, and
  12. it offers speed and flexibility unmatched by any other language.  Indeed,
  13. assembly language is in many ways the ultimate programming language because
  14. it lets you control fully every aspect of your PC's operation.  Anything
  15. that a PC is capable of doing can be accomplished using assembly language. 
  16. This final chapter explains assembly language in terms that most BASIC
  17. programmers can understand.
  18.    Why, you might ask, would a BASIC programmer be interested in assembly
  19. language?  After all, the whole point of a high-level language such as
  20. BASIC is to shield the programmer from the underlying hardware.  Without
  21. having to worry about CPU registers and memory addresses, a BASIC
  22. programmer can be immediately productive, and probably write programs with
  23. fewer initial bugs.  However, there are three important reasons for using
  24. assembly language:
  25.  
  26.    ■ To speed up selected portions of a program
  27.  
  28.    ■ To reduce the size of a program
  29.  
  30.    ■ To perform services that BASIC simply cannot
  31.  
  32. It is important to understand that any high-level language will benefit
  33. from the appropriate use of assembler.  And while it is possible to write
  34. a major application using only assembly language, the increased complexity
  35. and added time to develop and debug it are often not worth the trouble. 
  36. Using a high-level language--especially BASIC--for the majority of a
  37. program and then coding the size- and speed-critical portions in assembly
  38. language often is the most practical solution.
  39.    Many BASIC programmers mistakenly believe that to achieve the fastest
  40. and smallest programs they should learn C.  In my opinion, nothing could
  41. be further from the truth.  Assembly language is barely more difficult to
  42. use than C, and in fact the code is often more readable.  Further, no
  43. high-level language can come even close to what raw 8086 code can achieve. 
  44. If you truly desire to become an advanced programmer, you owe it to
  45. yourself to at least see what assembly language is all about.  I believe
  46. there is no deeper satisfaction than that gained by understanding fully
  47. what your computer is doing at the lowest level.
  48.    This chapter assumes that you already understand basic programming
  49. concepts such as variables, arrays, and subroutines.  As we proceed, most
  50. of the examples will provide parallels to BASIC where possible.  But please
  51. remember one important point: There is nothing inherently difficult about
  52. assembly language.  Attitude is everything, and if you can think of
  53. assembler as a stripped-down version of BASIC, you will be successful that
  54. much sooner.
  55.    For ease of reading, I will refer to the 8088 microprocessor used in the
  56. IBM PC throughout this chapter.  However, everything said about the 8088
  57. also applies to the 8086, the 80286, the 80386/486, and the NEC V series
  58. found in some older PC compatible computers.  I will also use the terms
  59. assembly language and assembler interchangeably, although assembler can
  60. also be used to mean the program that assembles your source files.
  61.    All of the examples in this chapter are meant to be assembled with the
  62. Microsoft Macro Assembler (MASM) version 5.1 or later.  MASM requires that
  63. you save your source files as standard ASCII text, and most word processor
  64. programs can do this.
  65.    Some of the examples in this chapter are derived from those that used
  66. CALL Interrupt in Chapter 11.  In most cases I have not bothered to restate
  67. the same information from that chapter, and you may want to refer back for
  68. additional information.
  69.    Finally, many entire books have been written about assembly language,
  70. and there is no way I can possibly teach you everything you need to know
  71. here.  Rather, my intent is to provide a gentle introduction to the
  72. concepts using practical and useful examples.
  73.  
  74.  
  75. AS EASY AS BASIC
  76. ================
  77.  
  78. Assembly language uses the same general form as a BASIC program.  That is,
  79. commands are performed in sequence until a GOTO or GOSUB is encountered. 
  80. In assembly language these are called Jump and Call, respectively.  Many
  81. BASIC instructions have a direct assembler equivalent, although the syntax
  82. is slightly different.  One important difference, however, is that the 8088
  83. microprocessor can operate on integer numbers only.  Another is that for
  84. the most efficiency, you are limited to only a few working variables.  I
  85. will begin by showing some rudimentary assembly language instructions, so
  86. you can see how they are analogous to similar commands in BASIC.  Consider
  87. the following BASIC program fragment:
  88.  
  89.    AX = 5
  90.  
  91. Here, the value 5 is assigned to the variable AX.  The 8088 has several
  92. built-in variables called *registers*, and one of them is called AX.  To
  93. move the value 5 into the AX register you use the Mov instruction:
  94.  
  95.    Mov AX,5
  96.  
  97. As with BASIC, the destination variable in an assembly language program
  98. is always shown on the left, and the source is on the right.  Now consider
  99. addition and subtraction.  To add the value 12 to AX in BASIC you do this:
  100.  
  101.    AX = AX + 12
  102.  
  103. The equivalent 8088 command is:
  104.  
  105.    Add AX,12
  106.  
  107. Again, the variable or register on the left is always the one that receives
  108. the results of any adding, moving, and so on.  Subtraction is very similar
  109. to addition, replacing Add with Sub:
  110.  
  111.        BASIC:  AX = AX - 100
  112.    Assembler:  Sub AX,100
  113.  
  114. Comparing and branching in assembly language is also quite similar to
  115. BASIC.  But instead of this:
  116.  
  117.    AX = AX + 2
  118.    IF AX > 60 GOTO Finished
  119.  
  120. You'd do it in assembler this way:
  121.  
  122.    Add AX,2
  123.    Cmp AX,60
  124.    Ja  Finished
  125.  
  126. This tells the 8088 to add 2 to AX, then compare AX to 60, and finally to
  127. *jump if above* to the code at label Finished.  There are several kinds of
  128. conditional jump instructions in assembly language, and they often follow
  129. a comparison as shown here.  In fact, all you can really do after a compare
  130. is jump somewhere based on the results.  And while there is no direct
  131. equivalent for this BASIC statement:
  132.  
  133.    IF AX = 10 THEN BX = BX - 1
  134.  
  135. You can change the strategy to this:
  136.  
  137.    IF AX <> 10 GOTO Not10
  138.    BX = BX - 1
  139.    Not10:
  140.     .
  141.     .
  142.  
  143. Now a direct translation is simple:
  144.  
  145.    Cmp AX,10
  146.    Jne Not10
  147.    Dec BX
  148.    Not10:
  149.     .
  150.     .
  151.  
  152. Jne stands for *Jump if Not Equal*.  Also, notice the command Dec, which
  153. means decrement by 1.  This is one case in which an assembler instruction
  154. is actually more to the point than its BASIC counterpart, and is equivalent
  155. to the BASIC command BX = BX - 1.  While Sub BX, 1 would work just as well,
  156. using Dec is faster and generates less code, and we all know that speed is
  157. the name of the game.
  158.    The complement to Dec is Inc, short for *increment by one*.  You can use
  159. Inc and Dec with most of the 8088's registers, as well as on the contents
  160. of any memory location, which brings up an important issue.  At some point,
  161. many programs will require more variables than can be held within the CPU's
  162. registers.  All of the available free memory in a PC can be used as
  163. variable storage, with only a few limitations:
  164.  
  165.    ■ You must first tell the assembler how much space to set aside, much
  166.    like you would when dimensioning an array. Moreover, MASM is pretty
  167.    friendly and lets you use names for the memory locations.  In fact, in
  168.    most cases you do not need to know the memory addresses variables will
  169.    be stored in--the assembler handles that for you as well!
  170.  
  171.    ■ Adding, subtracting, incrementing, and decrementing are all much
  172.    faster when done within registers.  When an operation is performed on
  173.    a memory variable, it must first be fetched by the CPU, manipulated, and
  174.    then stored again.  Because the registers are within the CPU chip, those
  175.    extra steps are not needed.  The steps to retrieve and then store memory
  176.    variables is handled transparently by the 8088; I mention this merely
  177.    to explain why register operations are faster.
  178.  
  179.    ■ Some operations can be done only using registers.  If you want to
  180.    multiply the memory variable Counter by 12, you first have to move the
  181.    variable into AX, do the multiplication, and then move it back into
  182.    memory again.  And if AX is currently holding a needed value, it must
  183.    be saved before multiplying and restored again afterward.  Although
  184.    assembly language is not as complicated as many people think, it surely
  185.    can be tedious at times.
  186.  
  187. Besides the CPU registers and conventional memory addresses, a special
  188. portion of memory called the *stack* is also available for storage.  The
  189. stack is much like the temporary memory on a four-function calculator, and
  190. it is often used to store intermediate results.  The stack is also commonly
  191. used to pass variables between programs, because all programs can access
  192. it without having to know exactly where in memory it is located.  Again,
  193. assembly language doesn't usually require you to deal with absolute memory
  194. addresses at all--especially for subroutines that will be added to a BASIC
  195. program.  The only exceptions might be when writing directly to the display
  196. screen, or when looking at low memory, perhaps to see whether the Caps Lock
  197. key is engaged.
  198.  
  199.  
  200. SPAGHETTI CODE?
  201.  
  202. To write a routine that converts lower case letters to capital letters in
  203. BASIC, you might use something like this:
  204.  
  205.    IF AL$ => "a" AND AL$ <= "z" THEN
  206.      AL$ = CHR$(ASC(AL$) - 32)
  207.    END IF
  208.  
  209. In assembly language each compare must be done separately, followed by a
  210. jump based on the results.  Let's rephrase the BASIC example slightly:
  211.  
  212.    IF AL$ < "a" GOTO Done
  213.    IF AL$ > "z" GOTO Done
  214.    AL$ = CHR$(ASC(AL$) - 32)
  215.    Done:
  216.     .
  217.     .
  218.  
  219. Now a conversion to assembler is easy:
  220.  
  221.    Cmp AL,"a"     ;compare AL to "a"
  222.    Jb  Done       ;Jump if Below to Done
  223.    Cmp AL,"z"     ;compare AL to "z"
  224.    Ja  Done       ;Jump if Above to Done
  225.    Sub AL,32      ;subtract 32 from AL
  226.    Done:
  227.     .
  228.     .
  229.  
  230. Notice how the assembler allows the use of quoted constants.  When it sees
  231. a character or string in double or single quotes, it knows you mean to use
  232. the character's ASCII value.  Unlike BASIC with its strong variable typing
  233. that prevents you from performing numeric operations on a string, assembly
  234. language has very few such restrictions.  Also notice how much jumping
  235. around is necessary to accomplish even the simplest of actions.
  236.    As I mentioned earlier, assembly language can certainly be more tedious
  237. than BASIC, although the logic is not really that different.  Such frequent
  238. jumping around is called spaghetti code by some programmers, and it is
  239. often used in a derogatory fashion when discussing BASIC's GOTO statement. 
  240. But this is the way that computers work, and I am amused by programmers who
  241. argue so strongly against all use of the GOTO command.  While nobody could
  242. seriously object to a well organized and structured programming style, all
  243. programs are eventually converted to equivalent assembly language jumps and
  244. branches.
  245.  
  246.  
  247. THE REGISTERS
  248. =============
  249.  
  250. There are six general purpose registers available for you to use: AX, BX,
  251. CX, DX, SI, and DI.  Each register may be used for the most common
  252. operations like adding and subtracting, although some are specialized for
  253. certain other operations.  However, most of the registers also have a
  254. specialty.  For example, AX is the only register that can be multiplied or
  255. divided.  The A in AX stands for Accumulator, and it often used for math
  256. operations such as accumulating a running total.  Also, several assembler
  257. instructions result in one byte less code when used with AX, when compared
  258. to the same instructions using other registers.
  259.    The B in BX means Base, and this register is frequently used to hold the
  260. base address of a collection of variables or other data.  If you have a
  261. text string in memory to be examined, you could put the address of the
  262. first character in BX.  The rest of the string can then be found by
  263. referencing BX.
  264.    BX can also be used to specify computed addresses using addition or
  265. subtraction.  For example, the instruction Mov AX,[BX+4] means to load AX
  266. with the word four bytes beyond the address held in BX.  Likewise, the
  267. instruction Add DL,[BX+SI-10] adds the value of the byte at that computed
  268. address to the current contents of DL.  You may use BX this way with either
  269. a constant number, the SI or DI register, or one of those registers and a
  270. constant number.  However, only addition and substraction may be used, as
  271. opposed to multiplication or division.  I will return to computed and
  272. indirect addressing later in this chapter.
  273.    The C in CX stands for Count, since CX is most often used as the counter
  274. in an assembly language FOR/NEXT loop.  In fact, the assembly language
  275. command Loop uses CX to perform an operation a specified number of times. 
  276. The comparison below illustrates this.
  277.  
  278.    BASIC:
  279.         FOR CX = 1 TO 5
  280.           GOSUB BeepTone
  281.         NEXT
  282.  
  283.    Assembler:
  284.         Mov  CX,5
  285.         Do:  Call Beep_Tone
  286.         Loop Do
  287.  
  288. Here, the Loop instruction automatically branches to the label Do: CX
  289. times.  That is much faster and more efficient than this:
  290.  
  291.    Mov  CX,5
  292.    Do:  Call Beep_Tone
  293.    Dec  CX
  294.    Cmp  CX,0
  295.    Jne  Do
  296.  
  297. The DX register is a general purpose Data register, and is named
  298. accordingly.  DX is also used in conjunction with AX when multiplying and
  299. dividing.
  300.    The last two general purpose registers are SI and DI.  SI stands for
  301. Source Index, while DI means Destination Index.  It is not hard to guess
  302. that these registers are well suited for copying data from one memory
  303. location to another.  The 8088 has a rich set of instructions for moving
  304. and comparing strings, using SI and DI to show where they are.
  305.    Like BX, SI and DI may be used with a constant offset such as [SI+100]
  306. to compute a memory address, or with a constant value and/or BX.  But
  307. again, SI and DI are still general purpose registers, and they can be used
  308. for common chores as well.  In many situations it really doesn't matter
  309. whether you use BX or DI or SI or AX.
  310.    There are two specialized registers called BP and SP.  BP (Base Pointer)
  311. is another Base register like BX, only it is intended for use with the
  312. stack.  When you need to access data on the stack, BP is the most
  313. appropriate register to use.  Like BX, BP can reference computed addresses
  314. with a constant offset, with SI or DI, or with a constant and SI or DI.
  315.    The SP (Stack Pointer) register holds the current address of the stack,
  316. and it should never be altered unless you have a very good reason to do so.
  317.    The last four registers are the segment registers, but I will mention
  318. them only briefly right now.  As you undoubtedly know, the 8088 used a
  319. segmented architecture; although it can utilize a megabyte of memory, it
  320. can do so only in 64K portions at a time.  The CS register holds the
  321. current Code Segment (your program code), DS holds the Data Segment (your
  322. memory variables), SS holds the Stack Segment, and ES is an Extra Segment
  323. that is often used to access arrays located in far memory.
  324.    Each of the 8088 registers can hold one word (two bytes), allowing you
  325. to store any integer number between 0 and 65535.  This range of values can
  326. also be considered as -32768 to 32767.  But AX, BX, CX, and DX may also be
  327. used as two separate one-byte registers with a range of either 0 to 255 or
  328. -128 to 127.  One byte is often sufficient--for example, when manipulating
  329. ASCII characters--and this ability to access each half individually
  330. effectively adds four more registers.  Remember, the more variables you can
  331. keep within registers, the faster and more efficient a program will be.
  332.    When using the registers separately, the two halves are identified by
  333. the letters H and L, for High and Low.  That is, the high portion of AX is
  334. referred to as AH, while the low portion of DX is called DL.  This would
  335. be represented with BASIC variables as follows:
  336.  
  337.    AX = AL + 256 * AH
  338.  
  339. Each half can also be represented as bit patterns:
  340.  
  341.               AX
  342.    ┌──────────────────────┐
  343.     1011  0110  0111  0101
  344.    └──────────┘└──────────┘
  345.         AH          AL
  346.  
  347. Notice that SI, DI, BP, and SP cannot be split this way, nor can the
  348. segment registers CS, DS, SS, and ES.
  349.    There is also another register called the Flags register, though it is
  350. not intended for you to use directly.  After performing calculations and
  351. comparisons, certain bits in the Flags register are set or cleared by the
  352. CPU automatically, depending on the results.  For example, if you add a
  353. register that holds the value 40000 to another register whose value is
  354. 30000, the Carry flag will be set to show that the result exceeded 64K. 
  355. The 8088 flags are also set or cleared to reflect the result of a Cmp
  356. (Compare) instruction.  Although you will not usually access these flags
  357. directly, they are used internally to process Jne, Ja, and the other
  358. conditional jump commands.
  359.  
  360.  
  361. VARIABLES IN ASSEMBLY LANGUAGE
  362. ============================== 
  363.  
  364. All of the example routines shown so far have used the 8088 registers as
  365. working variables.  Indeed, using registers whenever possible is always
  366. desirable because they can be accessed very quickly.  But in many real-
  367. world applications, more variables are needed than can fit into the few
  368. available registers.  As with BASIC, MASM lets you define variables using
  369. names you choose, and you must also specify the size of each variable.
  370.    The first step is to define the amount of space that will be set aside
  371. with the assembler instructions DB and DW.  These stand for Define Byte and
  372. Define Word respectively, and they allocate either one byte of storage or
  373. two.  You can also use DD to define a double word long integer variable. 
  374. Notice that these are not commands that the 8088 processor will execute;
  375. rather, they inform the assembler to leave room for the data.  Some
  376. examples are shown below:
  377.  
  378.    MyByte DB 12h                     ;one byte, preset to 12h
  379.    Buffer DB 15 Dup(0)               ;fifteen bytes, all 0
  380.    Dummy  DW ?                       ;one word (two bytes), 0
  381.    Msg    DB "Test message",13,10    ;message, CR, LF
  382.  
  383. In the first example one byte of memory is allocated using the name MyByte,
  384. and the value 12 Hex is placed there at assembly time.  The second example
  385. illustrates using the Dup (duplicate) command, and tells MASM to set aside
  386. fifteen bytes filling each with the specified value.  In this case that
  387. value is zero.  Initialized data is an important feature of assembly
  388. language, and one that is sorely missing from BASIC.  By being able to
  389. allocate data values at assembly time, additional code to assign those
  390. values at runtime is not needed.
  391.    Filling an area with zeroes can also be accomplished with a question
  392. mark, and this is frequently used when the value that will eventually end
  393. up there is not known in advance.  Both do the same thing in most cases,
  394. however using "?" implies an unknown, as opposed to an explicit zero.  You
  395. may use whichever method seems more appropriate at the time.  The last
  396. example shows how text may be specified, as well as combining values in a
  397. single statement.
  398.    Since the assembler lets you use names for your data, fetching or
  399. storing values can be done with the normal Mov instruction like this.
  400.  
  401.    Error_Code  DB ?
  402.    Mov Error_Code,AL
  403.  
  404. This puts the contents of register AL into memory location Error_Code. 
  405. Getting it back again later is just as easy:
  406.  
  407.    Mov DH,Error_Code
  408.  
  409. Sometimes the assembler needs a little help when you assign variables. 
  410. When you move AL or DH in and out of a memory location, the assembler knows
  411. that you are dealing with a single byte.  And if you specify BX or SI as
  412. the source or destination operand, the assembler understands this to mean
  413. two bytes, or one word.  But when literal numbers are used, the size of the
  414. value is not always obvious.  Consider the following:
  415.  
  416.    Mov [BX],3Ch
  417.  
  418. Does this mean that you want to put the value 3Ch into the byte at the
  419. address held in BX, or the value 003Ch into the *word* at that address? 
  420. There is no way for MASM to know what your intentions are, so you must
  421. specify the size explicitly.  This is done with the Byte Ptr and Word Ptr
  422. directives.  Here, Ptr stands for Pointer, and two examples are shown: 
  423.    Mov Byte Ptr [BX],15
  424.    Mov Word Ptr ES:[DI],100
  425.  
  426. The first example specifies that the memory at address BX is to be treated
  427. as a single byte.  Had Word been used instead, a 15 would be placed into
  428. the byte at address held in BX, and a zero would be put into the byte
  429. immediately following.  Words are always stored with the low-byte before
  430. the high-byte in memory.
  431.    Memory variables are accessed using the normal complement of
  432. instructions.  For example, to add 15 to the variable Counter you will use
  433. Add Counter,15.  And to multiply AX by the word variable Number you will
  434. use Mul Word Ptr Number.  In MASM versions 5.0 and later, the Word Ptr
  435. argument is not strictly necessary.  That is, if Number had been defined
  436. using DW, then MASM knows that you mean to multiply by a word rather than
  437. a byte.  But earlier versions of the assembler were not so smart, and an
  438. explicit Word Ptr or Byte Ptr was required.
  439.    Note, however, that you must still use Byte Ptr or Word Ptr to override
  440. a variable's type.  For example, if Value was defined as a word but you
  441. want to access just its lower byte, you must use Mov AL,Byte Ptr Value. 
  442. Here, stating Byte Ptr explicitly tells MASM that you are intentionally
  443. treating Value as a different data type.  Otherwise, it will issue a non-
  444. fatal warning error message.
  445.    Sometimes you may want to refer to the address of a variable, as opposed
  446. to its contents.  For example, Mov AX,Variable tells MASM to move the value
  447. held in Variable into the AX register.  But many DOS services require that
  448. you specify a variable's address in a register.  This is done using the
  449. Offset operator:  Mov DX,Offset Buffer.  Where Mov DX,Buffer places the
  450. first two bytes of the buffer into DX, using Offset tells MASM that you
  451. instead want the starting address of the buffer.
  452.    You can also use the Lea (Load Effective Address) command to obtain an
  453. address, but that is less frequently used.  Although Lea DX,Buffer can be
  454. used to load DX with the starting address of Buffer, it is a slightly
  455. slower instruction.  Lea is needed only when an address must be computed. 
  456. For example, the instruction Lea SI,[BX+DI] loads SI with the sum of the
  457. BX and DI registers.  You may notice that Lea can provide a shortcut for
  458. adding or subtracting certain register combinations.  Although this use of
  459. Lea is uncommon, Lea can replace the following two instructions:
  460.  
  461.    Mov SI,BX
  462.    Add SI,DI
  463.  
  464. To subtract two registers or a register and a constant value you could use
  465. Lea AX,[BX-DI] or Lea SI,[BP-10].
  466.  
  467.  
  468. CALCULATIONS IN ASSEMBLY LANGUAGE
  469. =================================
  470.  
  471. When adding or subtracting you may use two registers, or a register and a
  472. memory variable.  It is not legal to specify two memory variables as in
  473. Add Var1,Var2.
  474.    Multiplying and dividing are not so flexible; only AL and AX may be
  475. multiplied.  When dividing, the numerator must be either in AX, or the long
  476. integer comprised of DX:AX.  In this case, DX holds the upper word and AX
  477. holds the lower one.  However, you may multiply or divide these registers
  478. using either a register or a memory location.  Because of this restriction,
  479. it is not necessary to specify the target operand size.  That is, Mul CL
  480. means to multiply AL by CL leaving the result in AX, and Div WordVariable
  481. divides DX:AX by the contents of WordVariable leaving the result in AX and
  482. the remainder in DX.  Although you could use the commands Mul AL,CL and Div
  483. AX,WordVariable, this is not necessary or common.
  484.    All of the allowable combinations for multiplying and dividing are shown
  485. in Figure 12-1.
  486.  
  487.  
  488. Instruction          Operand    Result    Remainder
  489. ════════════════     ═══════    ══════    ═════════
  490. Mul ByteRegister        AL        AX         n/a
  491. Mul ByteVariable        AL        AX         n/a
  492. Mul WordRegister        AX       DX:AX       n/a
  493. Mul WordVariable        AX       DX:AX       n/a
  494.  
  495. Div ByteRegister        AX        AL          AH
  496. Div ByteVariable        AX        AL          AH
  497. Div WordRegister      DX:AX       AX          DX
  498. Div WordVariable      DX:AX       AX          DX
  499.  
  500. Figure 12-1: The allowable register/memory combinations for multiplying and
  501. dividing.
  502.  
  503.  
  504. In Figure 12-1 ByteRegister means any byte-sized register such as AL or
  505. CH; WordRegister indicates any word-sized register like CX or BP. 
  506. Likewise, ByteVariable and WordVariable specify byte- and word-sized
  507. integer memory variables respectively.
  508.    It's important to understand that you must never divide by zero, because
  509. that will generate a critical error.  Because the result from dividing by
  510. zero is infinity, the 8088 has no way to handle that--it can't simply
  511. ignore the error.  Therefore, dividing by zero causes the CPU to generate
  512. an Interrupt 0.  In a BASIC program that error is routed to BASIC's
  513. internal error handling mechanism which either invokes the ON ERROR handler
  514. if one is in effect, or ends your program with an error message.  In a
  515. purely assembly language program, DOS intervenes printing an error message
  516. on the screen, and then it ends the program.
  517.    Related to division by zero is dividing when the result cannot fit into
  518. the destination register.  For example, if AX holds the value 20000 and you
  519. divide it by 2, the resulting 10000 cannot fit into AL.  Since this is
  520. another unrecoverable error that cannot be ignored, the 8088 generates an
  521. Interrupt 0 there as well.
  522.    Besides the Div and Mul instructions, there are also signed versions
  523. called Idiv and Imul.  Where Div and Mul treat the contents of AX or DX:AX
  524. as an unsigned value, Idiv and Imul treat them as being signed.  You'll use
  525. whichever command is appropriate, so the 8088 knows if values having their
  526. highest bit set are to be treated as negative.  BASIC always uses Idiv and
  527. Imul in the code it generates, since all integer and long integer values
  528. are treated by BASIC as signed.
  529.    Because only AX and DX:AX may be used for multiplying and dividing, this
  530. affects your choice of registers.  The short example that follows shows how
  531. you might select registers when translating a simple BASIC-like expression
  532. that uses only integer (not long integer) variables.
  533.  
  534.  
  535.    BASIC:
  536.         Result = (Var1 + Var2 * (Var3 - Var4)) \ 100
  537.  
  538.  
  539.    Assembler:
  540.         Mov  AX,Var3          ;work from the innermost level out
  541.         Sub  AX,Var4          ;so first perform Var3 - Var4
  542.         Imul Word Ptr Var2    ;then multiply that by Var2
  543.         Add  AX,Var1          ;add Var1 to what we have so far
  544.         Mov  DX,0             ;next prepare to divide DX:AX
  545.         Mov  CX,100           ;use CX for the divisor
  546.         Idiv CX               ;do the division
  547.         Mov  Result,AX        ;then assign Result ignoring the
  548.                               ;  remainder left in DX
  549.  
  550.  
  551. Because dividing by an integer value uses both DX and AX, it is necessary
  552. to clear DX explicitly as shown unless you are certain it is already zero. 
  553. The use of CX to hold the value 100 is arbitrary.  If CX were currently in
  554. use, any available word-sized register or memory location could be used. 
  555. If you compile this program statement and view the resultant code using
  556. CodeView, you will see that BASIC does an even better job of translating
  557. this particular expression to assembly language.
  558.  
  559.  
  560. STRING PROCESSING INSTRUCTIONS
  561. ==============================
  562.  
  563. Besides being able to add, subtract, multiply, and divide, the 8088
  564. provides four very efficient instructions for manipulating strings and
  565. other data in memory.  Movs copies, or moves a string from place to
  566. another; Cmps compares two ranges of memory; Stos fills, or stores one or
  567. more addresses with the same value; and Scas scans a range of memory
  568. looking for a particular value.  These instructions require either a byte
  569. or word specifier.  For example, you would use Movsb to copy a byte, and
  570. Cmpsw to compare two words.
  571.    There are two important factors that contribute to the power and
  572. usefulness of these string instructions: each is only one byte long, and
  573. they automatically increment or decrement the SI and DI registers that
  574. point to the data being manipulated.  Thus, they are both convenient to
  575. use, and also very fast.  Because it is common to access blocks of memory
  576. sequentially a byte or word at a time, automatically advancing SI and DI
  577. saves you from having to do that manually with additional instructions. 
  578. For example, after one pair of words has been compared, SI and DI are
  579. already set to point at the next pair.
  580.    You can also specify that SI and DI are to be decremented by first using
  581. the Std (Set Direction) command.  The Direction Flag stores the current
  582. string operations direction, which is either up or down.  If a previous Std
  583. was in effect, then you'd use Cld (Clear Direction) to force copying and
  584. moving to be forward.  In fact, BASIC *requires* you to clear the direction
  585. flag to forward before returning from a routine that set it to backwards.
  586.  
  587.  
  588. MOVS AND CMPS
  589.  
  590. Movs and Cmps use the DS:SI register pair to point to the first range of
  591. memory being copied or compared, and ES:DI to point to the second range. 
  592. Each time a byte is being copied or compared, SI and DI are incremented or
  593. decremented by one to point to the next address.  And when a word is being
  594. accessed, SI and DI are incremented or decremented by two.
  595.    Notice that there is no protection against SI or DI being incremented
  596. or decremented through address zero, nor is there any indication that this
  597. has happened.  Also notice that the name Movs is somewhat of a misnomer. 
  598. To me, moving something implies that it is no longer at its original
  599. location.  Movs does not alter the source data at all--it merely places a
  600. new copy at the specified destination address.
  601.  
  602.  
  603. SCAS AND STOS
  604.  
  605. Scas compares the value in AL or AX with the range of memory pointed to
  606. by ES:DI.  That is, Scasb compares AL and Scasw uses AX.  Stos also uses
  607. ES:DI to show where the data being written to is located; Stosb stores the
  608. contents of AL in the address at ES:[DI] and then increments or decrements
  609. DI by one.  Likewise, Stosw stores the value in AX there and increments or
  610. decrements DI by two.
  611.  
  612.  
  613. REPEATING STRING OPERATIONS
  614.  
  615. If these four instructions merely acted on the data and incremented SI and
  616. DI automatically, that would be very useful indeed.  But they also have
  617. another talent: they recognize a Rep (Repeat) prefix to perform their magic
  618. a specified number of times.  The number of iterations is specified by the
  619. count held in CX.  Furthermore, the number of repetitions can be made
  620. conditional when comparing and scanning, based on the data encountered.
  621.    If you have, say, 20 bytes of data that need to be copied from one place
  622. to another, you would first set CX to 20 and then use Rep Movsb.  And to
  623. compare 100 words you would load CX with the value 100 and use Rep Cmpsw. 
  624. Stos also accepts a Rep prefix; Rep Stosb places the value in AL into CX
  625. bytes of contiguous memory starting at the address specified in ES:DI.  For
  626. each iteration the 8088 decrements CX, and when it reaches zero the copying
  627. or comparing is complete.
  628.    It is usually not valuable to scan a range of memory unconditionally and
  629. repeatedly.  Therefore Scas is generally used in conjunction with either
  630. Repe (Repeat while Equal) or Repne (Repeat while Not Equal).  Cmps is also
  631. generally used with these conditional prefixes, to avoid wasting time
  632. comparing bytes after a match or a difference was found.  In either case,
  633. however, you load CX with the total number of bytes or words being compared
  634. or scanned.
  635.    Because each iteration decrements CX, you can easily calculate how many
  636. bytes or words were actually processed.  Also, you can test the results of
  637. scanning and comparing using the normal methods such as Je and Jne.  The
  638. following few examples show some ways these commands can be used.
  639.  
  640.    See if two 40-byte ranges of memory are the same:
  641.  
  642.         Mov  CX,20              ;comparing 20 words is faster than 40 bytes
  643.         Repe Cmpsb              ;compare them
  644.         Je   Match              ;they matched
  645.  
  646.    Copy a 2000-element integer array to color screen memory:
  647.  
  648.         Mov  AX,ArraySeg        ;set DS to the source segment
  649.         Mov  DS,AX              ;through AX
  650.         Mov  SI,ArrayAdr        ;point SI to the array start
  651.         Mov  AX,&HB800          ;the color text screen segment
  652.         Mov  ES,AX              ;assign that to ES
  653.         Mov  DI,0               ;clear DI to point to address 0
  654.         Mov  CX,2000            ;prepare to copy 2000 words
  655.         Rep  Movsw              ;copy the data
  656.  
  657.    Search a DOS string looking for a terminating zero byte:
  658.  
  659.         Mov  AX,StringSeg       ;set ES to the string's segment
  660.         Mov  ES,AX              ;(ES cannot be assigned directly)
  661.         Mov  DI,Offset ZString  ;point DI to the string data
  662.         Mov  CX,80              ;search up to 80 bytes
  663.         Mov  AL,0               ;looking for a zero value
  664.         Repne Scasb             ;while ES:[DI] <> AL
  665.         ;-- Now DI points just past the terminating zero byte.
  666.         ;-- The length of the string is (80 - CX + 1).
  667.  
  668. In the first example, it is assumed that DS:SI and ES:DI already point to
  669. the correct segment and address.  By asking to compare only while the bytes
  670. are equal, the result of the most recent byte comparison can be tested
  671. using Je.  A common mistake many programmers make is comparing the bytes,
  672. and then checking if CX is zero.  The reasoning is that if CX is zero then
  673. they must have all matched; otherwise, the 8088 would have aborted the
  674. comparisons early.  But CX will also be zero if all but the last byte
  675. matched!  Therefore, you must check the zero flag using Je (or Jne if that
  676. is more appropriate).
  677.    Notice in the first example how 20 words are compared, rather than 40
  678. bytes.  Although the net result is the same, word operations are faster on
  679. 80286 and later processors when the blocks of memory begin at an even
  680. numbered address.  [Though you can't always know if a variable or block
  681. of memory will begin at an even address, using the word version will be
  682. more efficient at least some of the time.]
  683.    The second and third examples include the code needed to set up the
  684. appropriate segment and address values in DS:SI and ES:DI.  Although this
  685. may seem like a lot of work, you can often do this setup only once and then
  686. use the same registers repeatedly within a routine.  Unfortunately, you
  687. are not allowed to assign a segment register from a constant number.  You
  688. must first assign the number to a conventional register, and then use Mov
  689. to copy it to the segment register.
  690.  
  691.  
  692. THE STACK
  693.  
  694. The primary purpose of the stack is to retain the return address of a
  695. program when a subroutine is called.  This is true not only for assembly
  696. language, but for BASIC as well.  For example, when you use the BASIC
  697. statement GOSUB 1200, BASIC must remember the location in memory of the
  698. next command to execute when the routine returns.  It does this by placing
  699. the address of the next instruction onto the stack *before* it jumps to the
  700. subroutine.  Then when a RETURN instruction is encountered, the address to
  701. return to is available.  The 8088 understands Calls and Returns directly,
  702. and it places and restores the addresses on the stack automatically.
  703.    The stack is not unlike a stack of books on a table, and one of its
  704. great advantages is that you don't need to know where in memory it is
  705. actually located.  Items can be placed onto the stack either manually with
  706. the Push instruction, or automatically by the 8088 processor as part of its
  707. handling of Call and Return statements.  Values are retrieved from the
  708. stack with the Pop command, among other methods.
  709.    One important feature of the stack is when items are added and removed,
  710. the stack pointer register is updated automatically to reflect the next
  711. available stack location.  Thus, a program can access items on the stack
  712. based on the stack pointer, rather than have to know the exact address at
  713. any given time.  This simplifies exchanging information between programs,
  714. since neither has to know how the other operates.  This mechanism also
  715. makes it possible for programs written in one language to communicate with
  716. subroutines written in another.
  717.    Figure 12-2 shows how the stack operates.
  718.  
  719.  
  720.            │
  721.            │
  722. ├──────────┤
  723. │  Item 1  │ <── first item that was pushed
  724. ├──────────┤
  725. │  Item 2  │ <── second item that was pushed
  726. ├──────────┤
  727. │  Item 3  │ <── third item that was pushed
  728. ├──────────┤
  729. │  Item 4  │ <── last item that was pushed (SP points here)
  730. ├──────────┤
  731. │   Next   │ <── next available stack location
  732. ├──────────┤
  733.            │ ┌── the stack grows downward
  734. │            │   as new items are added
  735.            │ │
  736. │            │
  737.              \/
  738.  
  739. Figure 12-2: The organization of the CPU stack.
  740.  
  741.  
  742. As each item is pushed onto the stack, it is placed two bytes below the
  743. address held in the stack pointer.  Then the stack pointer is decremented
  744. by two, to show the next available stack location.  Therefore, the stack
  745. grows downward as new items are added.  Note that only full words may be
  746. pushed onto the stack, so all of the items shown here are two bytes in
  747. size.  Also note that the stack pointer holds the address of the last item
  748. that was pushed.
  749.  
  750.  
  751. PASSING PARAMETERS
  752. ==================
  753.  
  754. Imagine you have a BASIC subroutine that does something to the variable X. 
  755. The code to assign X, process, and print X might look like this:
  756.  
  757.    X = 12
  758.    GOSUB 2000     'the routine at line 2000 manipulates X
  759.    PRINT X
  760.  
  761. In assembly language you could push the value 12 onto the stack, and then
  762. call the subroutine.  The subroutine, expecting the value there would
  763. retrieve it, do its work, and then place the result back again before
  764. returning.  This is similar, but not identical, to how variables are passed
  765. between programs.  Most high-level languages including BASIC pass variables
  766. to subroutines by placing their *addresses* on the stack.  A called routine
  767. can then access the variable via its address, either to read it or to
  768. assign a new value.
  769.    If BASIC let you access the registers directly, it could pass variables
  770. through them, as you saw when telling DOS which of its services to do.  But
  771. BASIC doesn't allow that and moreover, with a limited number of registers,
  772. only a few variables or addresses could be accommodated.  The stack can
  773. hold any number of arguments, by pushing the address of each in turn.
  774.    When you use the BASIC CALL command and pass a variable name to a SUB
  775. or FUNCTION procedure, BASIC first pushes the address of that variable onto
  776. the stack, before jumping to the code being called.  And if more than one
  777. variable is specified, all of the addresses are pushed.  The example below
  778. shows how you might call a routine that returns the current default drive.
  779.  
  780.    CALL GetDrive(Drive%)
  781.  
  782. When GetDrive begins, it knows that the stack is holding the address of
  783. Drive%.  The segment and address of the calling BASIC program is also on
  784. the stack; however, GetDrive is not concerned with that.  The important
  785. point is that it can find the address on the stack using the SP (Stack
  786. Pointer) register.  When GetDrive begins the stack is set up as shown in
  787. Figure 12-3.
  788.  
  789.  
  790.            │ ^
  791. │            │
  792.            │ │
  793. │            └── higher addresses
  794. ├──────────┤
  795. │  Drive%  │ <── the address of Drive% that BASIC pushed
  796. ├──────────┤
  797. │ Ret Seg  │ <── BASIC's segment to return to
  798. ├──────────┤
  799. │ Ret Adr  │ <── BASIC's address to return to (SP holds this address)
  800. ├──────────┤
  801. │   Next   │ <── the next available stack location
  802. ├──────────┤
  803.            │
  804.            │
  805.  
  806. Figure 12-3: The state of the stack within a procedure when one variable
  807. address was passed.
  808.  
  809.  
  810. Notice that while GetDrive can get at the address of Drive% through SP,
  811. an extra step is still required to get at the *data* held in Drive%.  Let's
  812. digress for a moment to reconsider the difference between memory addresses
  813. and values.  The assembler command Mov AX,12 puts the value 12 into
  814. register AX.  But suppose you want to put the contents of *memory location*
  815. 12 into AX.  You indicate this to the assembler by using brackets, as shown
  816. in the two equivalent examples following.
  817.  
  818.    Mov AX,[12]    ;load AX from address 12
  819.  
  820.    Mov BX,12      ;assign BX to the value 12
  821.    Mov AX,[BX]    ;load AX from the address held in BX
  822.  
  823. The first statement loads AX from the contents of memory at address 12. 
  824. The second first loads BX with the number 12, and then uses BX to identify
  825. that address, moving the contents of that address into AX.  This is an
  826. important distinction, and is illustrated in Figure 12-4 using parallels
  827. to BASIC's PEEK and POKE commands.
  828.  
  829.  
  830.      BASIC                      Assembler
  831. ════════════════════       ═════════════════════
  832. BP = SP                    Mov BP,SP
  833. AL = PEEK(BP + 8)          Mov AL,[BP+8]
  834. SI = 12                    Mov SI,12
  835. POKE SI, 12                Mov Byte Ptr [SI],12
  836.  
  837. Figure 12-4: Similarities between BASIC's PEEK and POKE, and the assembly
  838. language Mov instruction.
  839.  
  840.  
  841. Although you can easily find the address of Drive% by looking at SP, an
  842. extra step is required to get at the actual value.  The example that
  843. follows shows how to do this, except there is one added complication.  You
  844. are not allowed to use SP for addressing, except with 386 and later
  845. microprocessors.  Since you undoubtedly want your programs to work with as
  846. many computers as possible, a different strategy must be used.
  847.    As I mentioned earlier, the BP register is a base register that is meant
  848. for accessing data on the stack.  Therefore, you must first copy SP into
  849. BP, and then use BP to access the stack.  Then you can find where Drive%
  850. is located, and put the current drive number into that address as shown
  851. following:
  852.  
  853.    Mov  BP,SP      ;put the current stack pointer into BP
  854.    Mov  SI,[BP+4]  ;put the address of Drive% into SI
  855.    Mov  AH,19h     ;tell DOS we want the default drive
  856.    Int  21h        ;call DOS to do it
  857.    Mov  [SI],AL    ;put the answer into Drive%
  858.  
  859. Notice how brackets are used to indicate the addresses.  You must first
  860. determine the address of Drive%'s address (whew!), before you can put the
  861. value held in AL there.  This is called indirect addressing, because a
  862. register is used to hold the address of the data.  Again, notice how the
  863. 8088 accepts addition on the fly when you tell it BP+4.
  864.    The complete working GetDrive routine has two small added complications. 
  865. Beside being unable to use SP for addressing memory, BASIC also requires
  866. you to not change BP either.  The obvious solution, therefore, is to first
  867. save BP on the stack before changing it, and then restore BP later before
  868. returning to BASIC.  The other complication is caused by the very fact that
  869. BASIC put extra information (Drive%'s address) onto the stack.  But neither
  870. is insurmountable, as shown here:
  871.  
  872.    Push BP          ;save BP before changing it
  873.    Mov  BP,SP       ;put the stack pointer into BP
  874.    Mov  SI,[BP+6]   ;put the address of Drive% into SI
  875.    Mov  AH,19h      ;tell DOS we want default drive
  876.    Int  21h         ;call DOS to do it
  877.    Mov  [SI],AL     ;put the answer into Drive%
  878.    Pop  BP          ;restore BP to its original value
  879.    Ret  2           ;return to BASIC
  880.  
  881. Notice that here, the address of Drive% is at [BP+6] rather than [BP+4]
  882. as it was in the previous listing.  Since BP was pushed at the start of the
  883. procedure, the stack pointer is two bytes lower when it is subsequently
  884. assigned to BP.  When SI is loaded, [BP] points to the saved version of
  885. itself, [BP+2] and [BP+4] point to the address and segment to return to,
  886. and [BP+6] holds the address of Drive%'s address.  This is illustrated in
  887. Figure 12-5.
  888.  
  889.  
  890.            │
  891.            │
  892. ├──────────┤
  893. │  Drive%  │ <── [BP+6] points here
  894. ├──────────┤
  895. │ Ret Seg  │ <── [BP+4] points here
  896. ├──────────┤
  897. │ Ret Adr  │ <── [BP+2] points here
  898. ├──────────┤
  899. │ Saved BP │ <── [BP] points here
  900. ├──────────┤
  901. │   Next   │ <── the next available stack location
  902. ├──────────┤
  903.            │
  904.            │
  905.  
  906. Figure 12-5: The state of the stack within a procedure after BP has been
  907. pushed.
  908.  
  909.  
  910. Normally when a Ret command is encountered, the 8088 pops the last four
  911. bytes from the stack automatically, and returns to the segment and address
  912. contained in those bytes.  But that would leave the 2-byte address of
  913. Drive% still cluttering up the stack.  To avoid this problem the 8088 lets
  914. you specify a *parameter count* as part of the Ret instruction.
  915.    For each variable address that is passed with a CALL from BASIC, you
  916. must add 2 to the Return instruction in your assembler routine.  This is
  917. the number of bytes to remove from the stack, with two being used for each
  918. incoming two-byte address.  Had two variables been passed, the program
  919. would have used Ret 4 instead.  Although it is possible to have the calling
  920. program clean up the stack itself, that would be wasteful.
  921.    For every occurrence of every call that passes parameters, BASIC would
  922. have to include additional code following the call to increment SP
  923. accordingly.  Pushing a parameter's address onto the stack leaves that much
  924. less stack space available.  Therefore, someone has to reverse the process
  925. and either pop the addresses or use Add SP,Num to adjust the stack pointer. 
  926. By having the called routine handle it, that code is needed only once.  In
  927. fact, this is an important deficiency of C, because by design C requires
  928. the caller to clean up the stack.
  929.    [If you've managed to persevere this far you'll be pleased to know that
  930. in practice, the assembler can be told to handle most or all aspects of
  931. stack addressing for you.  This is discussed in the sections that follow.]
  932.    It is also possible to tell BASIC to pass some types of parameters by
  933. value using the BYVAL option in the DECLARE or CALL statements.  When BYVAL
  934. is used, BASIC places the actual value of the variable onto the stack,
  935. rather than its address.  This has several important benefits.  First, the
  936. assembly language routine can use one less instruction.  Second, when a
  937. constant number is passed, BASIC does not need to make a copy of it in
  938. DGROUP.  This copying was described in Chapter 2.
  939.    However, BYVAL is appropriate only when a parameter does not have to be
  940. returned, and only when the values are integers.  If you pass a double
  941. precision parameter using BYVAL, all eight bytes are placed on the stack
  942. using four separate instructions rather than only two needed to pass the
  943. address.  You can also instruct BASIC to pass the full, segmented address
  944. of a parameter, and that is discussed in the section "Dynamic Arrays."
  945.  
  946.  
  947. PROCEDURES IN ASSEMBLY LANGUAGE
  948. ===============================
  949.  
  950. All of the discussions so far have focused on how to write the instructions
  951. for an assembly language subroutine.  However, none have described how
  952. these routines are added to a BASIC program, or how a complete procedure
  953. is defined.  Furthermore, the previous examples have not shown a key step
  954. that is needed with all such external routines: establishing the code and
  955. data segments.
  956.    Before an external routine can be linked to a BASIC program you must
  957. establish a public procedure name that LINK can identify.  I will first
  958. show the formal method for defining a procedure and its segments, and then
  959. show the newer, simplified methods that were introduced with MASM version
  960. 5.1.  The simplified syntax is used for all of the remaining examples in
  961. this chapter [so don't worry if the setup details for this first example
  962. appear overwhelming].
  963.    The simplest complete subprogram you are likely to encounter is probably
  964. the PrtSc routine that follows--all it does is call Interrupt 5 to send the
  965. contents of the current display screen to LPT1.
  966.  
  967.  
  968. Code    Segment Word Public 'Code'
  969. Assume  CS:Code
  970. Public  PrtSc
  971. PrtSc   Proc Far       ;this is equivalent to SUB PrtSc STATIC in BASIC
  972.  
  973. Int  5                 ;call BIOS interrupt 5
  974. Ret                    ;return to BASIC
  975.  
  976. PrtSc   Endp           ;this is equivalent to BASIC's END SUB
  977. Code    Ends
  978. End
  979.  
  980.  
  981. The first three lines tell the assembler that the code is to be placed in
  982. the segment named Code, and that the name PrtSc is to be made public.  The
  983. fourth line defines the start of a procedure.  The actual code occupies
  984. the next two lines.  Of course, you must tell the assembler where the
  985. procedure ends, which in this case is also the end of the code segment. 
  986. Had several procedures been included within the same block of code, each
  987. procedure would show a start and end point, but there would only be a
  988. single code segment.  The final End statement is needed to tell the
  989. assembler that this is the end of listing, although you might think that
  990. MASM would be smart enough to figure that out by itself!
  991.    Notice that there are two kinds of procedures: Far and Near. External
  992. routines that are called from BASIC are always Far, because BASIC uses what
  993. is called a *medium model*.  This means the procedure does not necessarily
  994. have to be within the same code segment as the main BASIC program.  The
  995. medium model allows the combined programs to exceed the usual 64k limit
  996. when linked to a final .EXE file.
  997.    When BASIC executes a CALL command, it uses a two-word address as the
  998. location to jump to.  One of the words contains a segment, and the other
  999. an address within that segment.  Then when your program finally returns,
  1000. the 8088 must know to remove two words from the stack--a segment and an
  1001. address--to find where to return to in the calling BASIC program.
  1002.    A near procedure, on the other hand, calls an address that is only one
  1003. word long.  And when the procedure returns, only a single word is popped
  1004. from the stack.  Again, the assembler does the bulk of the dirty work for
  1005. you.  You just have to remember to use the word Far.
  1006.  
  1007.  
  1008. SIMPLIFIED DIRECTIVES
  1009.  
  1010. Fortunately, Microsoft realized what a pain dealing with segments and
  1011. procedures and offsets from BP can be, and they enhanced MASM beginning
  1012. with version 5.0 to handle these details automatically for you.  Rather
  1013. than require the programmer to define the various code and data segments,
  1014. all that is needed are a few simple key words.
  1015.    The first is .Model Medium, which tells MASM that the procedures that
  1016. follow will be Far.  Used in conjunction with .Code and .Data, .Model
  1017. Medium tells MASM that any data you define should be placed into a group
  1018. named DGROUP.  Adding ,Basic after the .Model directive also declares your
  1019. procedures as Public automatically, so BASIC can access them when your
  1020. program is linked.
  1021.    By using the name DGROUP, the linker automatically gathers all of your
  1022. DB and DW data variables, and places them into the same segment that BASIC
  1023. uses.  While this has the disadvantage of impinging on BASIC's near data
  1024. space, it also means that on entry to the routine the DS register (which
  1025. BASIC sets to hold the DGROUP segment) hold the correct segment value for
  1026. your variables as well.
  1027.    To show the advantages of simplified directives, contrast the earlier
  1028. PrtSc with this version that does exactly the same thing:
  1029.  
  1030.  
  1031. .Model Medium, Basic
  1032. .Code
  1033.  
  1034. PrtSc Proc
  1035.   Int 5
  1036.   Ret
  1037. Endp
  1038. End
  1039.  
  1040.  
  1041. MASM 5.1 introduced additional simplified directives that let you access
  1042. incoming parameters by name, rather than as offsets from BP.  All of the
  1043. remaining examples in this chapter take advantage of simplified directives,
  1044. as the following revised listing for GetDrive illustrates.
  1045.  
  1046.  
  1047. ;Syntax: CALL GetDrive(Drive%)
  1048.  
  1049. .Model Medium, Basic
  1050. .Data
  1051.    ;-- if variables were needed they would be placed here
  1052.  
  1053. .Code
  1054. GetDrive Proc, Drive:Word
  1055.  
  1056.   Mov  AH,19h      ;tell DOS we want the default drive
  1057.   Int  21h         ;call DOS to do it
  1058.   Mov  BX,Drive    ;put the address of Drive% into BX
  1059.   Cbw              ;clear AH to make a full word
  1060.   Mov  [BX],AL     ;then store the answer into Drive%
  1061.   Ret              ;return to BASIC
  1062.  
  1063. GetDrive Endp      ;indicate the end of the procedure
  1064. End                ;and the end of the source file
  1065.  
  1066.  
  1067. As you can see, this looks remarkably like a BASIC SUB or FUNCTION
  1068. procedure, with the incoming parameter listed by name and type as part of
  1069. the procedure declaration.  This greatly simplifies maintaining the code,
  1070. especially if you add or remove parameters during development.  If incoming
  1071. parameters are defined as shown here using Drive%, code to push BP and then
  1072. move SP into BP is added for you automatically.  When you refer to one of
  1073. the parameters, the assembler substitutes [BP+##] in the code it generates. 
  1074. Note, however, that the Word identifier for Drive refers to the 2-byte size
  1075. of its address, and not the fact that Drive% is a 2-byte integer.
  1076.    Also notice the new Cbw command, which is used here to clear the AH
  1077. register.  Cbw (Convert Byte to Word) expands the byte value held in AL to
  1078. a full word in AX.  A full word is needed to ensure that both the high- and
  1079. low-byte portions of Drive% are assigned, in case it held a previous value. 
  1080. If the value in AL is positive (between 0 and 127), AH is simply cleared
  1081. to zero.  And if AL is negative (between -128 and -1 or between 128 and
  1082. 255), Cbw instead sets all of the bits in AH to be on.  Thus, the sign of
  1083. the original number in AL is preserved.
  1084.    A complementary statement, Cwd (Convert Word to Double Word), converts
  1085. the word in AX to a double-word in DX:AX.  Again, if AX is positive when
  1086. considered as a signed number, DX is cleared to zero.  And if AX is
  1087. currently negative, DX is set to FFFFh (-1) to preserve the sign.  Cbw and
  1088. Cwd are both one-byte instructions, so even with unsigned values they are
  1089. always smaller and faster for clearing AH or DX than Mov AH,0 and Mov DX,0
  1090. which require two bytes and three bytes respectively.
  1091.    Finally, the Ret command that exits the procedure is translated by MASM
  1092. to include the correct stack adjustment value, based on the number of
  1093. incoming parameters.  If you have multiple exit points from the procedure
  1094. (equivalent to EXIT SUB), the exit code will be generated multiple times. 
  1095. That is, each occurrence of Ret is replaced with a code sequence to pop the
  1096. saved registers, and preform the 3-byte Ret # instruction.  Therefore, you
  1097. should always use a single exit point in a routine, and jump to that when
  1098. you need to exit from more than one place.
  1099.  
  1100.  
  1101. CALLING INTERRUPTS
  1102. ==================
  1103.  
  1104. Chapter 11 explained how interrupts work, and mentioned that only assembly
  1105. language can call an interrupt directly.  An assembler program uses the Int
  1106. instruction, and this tells the 8088 to look in the interrupt vector table
  1107. in low memory to obtain the interrupt procedure's segment and address. 
  1108. Then the procedure is called as if it were a conventional subroutine.
  1109.    All of the DOS and BIOS services are accessed using interrupts, though
  1110. there are so many different services that you also have to pass a service
  1111. number to many of them.  Most of the DOS services are accessed through
  1112. interrupt 21h.  Where BASIC uses the &H prefix to indicate a hexadecimal
  1113. value, assembly language uses a trailing letter H.  If you specify a number
  1114. without an H it is assumed by MASM to be regular decimal.  Note that MASM
  1115. doesn't care if you use upper- or lowercase letters, and knows that either
  1116. means hexadecimal.
  1117.    When specifying hexadecimal values to MASM, the first character must
  1118. always be a digit.  That is, 1234h is acceptable, but &HB800 must be
  1119. entered as 0B800h.  Using B800h will generate a syntax error.
  1120.  
  1121.  
  1122. DOS AND BIOS SERVICES
  1123.  
  1124. You have already seen how to call the BIOS routine that prints the screen
  1125. and the DOS routine that returns the current drive.  Let's continue and see
  1126. how to call some of the other useful routines in the BIOS and DOS.
  1127.    The next example program, DosVer, shows how to call the DOS service that
  1128. returns the DOS version number.  Like many of the assembler routines that
  1129. you can use with BASIC, DosVer relies on an existing DOS service to do the
  1130. real work.  In this program you will also learn how to push and pop values
  1131. on the stack.
  1132.    The syntax for DosVer is CALL DosVer(Version%), where Version% returns
  1133. with the DOS version number times 100.  That is, if your PC is running DOS
  1134. version 3.30, then Version% will be assigned the value 330.  Manipulating
  1135. floating point numbers is much more difficult than integers, and the added
  1136. complexity is not justified for this routine.
  1137.    The DOS service that retrieves the version number returns with two
  1138. separate values--the major version number (3 in this case) and the minor
  1139. number (30).  These values are returned in AL and AH respectively.  The
  1140. strategy here is to first multiply AL by 100, and then add AH.  The last
  1141. step is to assign the result to the incoming parameter Version%.
  1142.    Unfortunately, when you use AL for multiplication, the value 100 must
  1143. be in a register or memory location.  You can't just use MUL AL,100 though
  1144. it would sure be nice if you could.  Further, whenever AL is multiplied the
  1145. result is placed into the entire AX register.  Therefore, DosVer also uses
  1146. BX to temporarily store the original contents of AX before the two are
  1147. added together.
  1148.    As you already have learned, the only register that can be multiplied
  1149. is AX, or its low-byte portion, AL.  MASM knows if you plan to multiply AX
  1150. or AL based on the size of the argument.  For example, Mul BX means to
  1151. multiply AX by BX and leave the result in DX:AX.  Mul CL instead multiplies
  1152. AL by CL and leaves the answer in AX.
  1153.    The complete DosVer routine is shown following, and comments explain
  1154. each step.
  1155.  
  1156.  
  1157. ;DOSVER.ASM, retrieves the DOS version number
  1158.  
  1159. .Model Medium, Basic
  1160. .Code
  1161.  
  1162. DOSVer Proc, Version:Word
  1163.  
  1164.   Mov  AH,30h      ;service 30h gets the version
  1165.   Int  21h         ;call DOS to do it
  1166.  
  1167.   Push AX          ;save a copy of the version for later
  1168.   Mov  CL,100      ;prepare to multiply AL by 100
  1169.   Mul  CL          ;AX is now 300 if running DOS 3.xx
  1170.  
  1171.   Pop  BX          ;retrieve the version, but in BX
  1172.   Mov  BL,BH       ;put the minor part into BL for adding
  1173.   Mov  BH,0        ;clear BH, we don't want it anymore
  1174.   Add  AX,BX       ;add the major and minor portions
  1175.  
  1176.   Mov  BX,Version  ;get the address for Version%
  1177.   Mov  [BX],AX     ;assign Version% from AX
  1178.   Ret              ;return to BASIC
  1179.  
  1180. DOSVer Endp
  1181. End
  1182.  
  1183.  
  1184. Notice the extra switch that is done with BH and BL.  AX is saved onto the
  1185. stack because multiplying the byte in AL leaves the result as a full word
  1186. in AX, thus destroying AH.  When the version is popped into BX, the minor
  1187. part is in BH.  But you are not allowed to add registers that are different
  1188. sizes (AX and BH).  Further, any number in the high half of a register is
  1189. by definition 256 times the value of the same number in a low half. 
  1190. Therefore, BH is first copied to BL to reflect its true value.  BH is then
  1191. cleared so it won't affect the result, and finally AX and BX are added.
  1192.    A better way to save AX and then restore it to BX would be to simply use
  1193. Mov BX,AX immediately after the call to Interrupt 21h.  I used Push and Pop
  1194. just to show how this is done.  As you can see, it is not necessary to pop
  1195. the same register that was pushed.  However, every Push instruction must
  1196. always have a corresponding Pop, to keep the stack balanced.  If a register
  1197. or other value is on the stack when the final Ret is encountered, that
  1198. value will be used as the return address which is of course incorrect.
  1199.    Division also acts on AX, or the combination of DX:AX.  When you use
  1200. the command Div BL, the 8088 knows you want to divide AX because BL is a
  1201. byte-sized argument.  It then leaves the result in AL and the remainder,
  1202. if any, is placed into AH.  Similarly, Div DX means that you are dividing
  1203. the long integer in DX:AX, because DX is a word.  The result of this
  1204. division is assigned to AX, with the remainder in DX.
  1205.  
  1206.  
  1207. ACCESSING BASIC STRINGS IN ASSEMBLY LANGUAGE
  1208. ============================================
  1209.  
  1210. As Chapter 2 explained, strings are stored very differently than regular
  1211. numeric variables.  BASIC lets you find the address of any variable with
  1212. the VARPTR function.  For integer or floating point numbers, the value
  1213. VARPTR returns is the address of the actual data.  But for strings, VARPTR
  1214. instead returns the address of a string descriptor.
  1215.    DOS employs a different method entirely for its strings, using a CHR$(0)
  1216. to mark the end.  This is describes separately later in the section "DOS
  1217. Strings."
  1218.  
  1219.  
  1220. BASIC NEAR STRINGS
  1221.  
  1222. A BASIC string descriptor is a table containing information about the
  1223. string--that is, its length and address.  In Microsoft compiled BASIC a
  1224. string descriptor is comprised of two words of information.  For QuickBASIC
  1225. and near strings when using BASIC PDS, the first word contains the length
  1226. of the string and the second holds the address of the first character. 
  1227. Consider the following BASIC instructions:
  1228.  
  1229.    X$ = "Assembler"
  1230.    V = VARPTR(X$)
  1231.  
  1232. V now holds the starting address of the four-byte descriptor for X$.  For
  1233. the sake of argument, let's say that V is now 1234.  Addresses 1234 and
  1234. 1235 will together contain the length of X$ which is 9, and addresses 1236
  1235. and 1237 will contain yet another address--that of the first character in
  1236. X$.  You can therefore find the length of X$ using this formula:
  1237.  
  1238.    Length = PEEK(V) + 256 * PEEK(V + 1)
  1239.  
  1240. And the first character "A" can be located with this:
  1241.  
  1242.    Addr = PEEK(V + 2) + 256 * PEEK(V + 3)
  1243.  
  1244. You could then print the string on the screen like this:
  1245.  
  1246.    FOR C = Addr TO Addr + 8
  1247.      PRINT CHR$(PEEK(C));
  1248.    NEXT
  1249.  
  1250. Therefore, this is a BASIC model for how strings are located by an assembly
  1251. language program.  When you call an assembler routine with a string
  1252. argument, BASIC first pushes the address of the descriptor onto the stack,
  1253. before calling the routine.  The next example is called Upper, because it
  1254. capitalizes all of the characters in a string.  Even though BASIC offers
  1255. the UCASE$ and LCASE$ functions, these are relatively slow because they
  1256. return a copy of the data that has been manipulated.  Upper instead
  1257. capitalizes the data in place very quickly.
  1258.    The strategy is to first get the descriptor address from the stack. 
  1259. Then Upper puts the length into BX and the address of the string data into
  1260. SI.  Upper steps through the string starting at the end, decrementing BX
  1261. by one for each character.  When BX crosses zero, it is done.  A BASIC
  1262. version is shown first, followed by the assembly language equivalent.
  1263.  
  1264. Upper in BASIC:
  1265.  
  1266. SUB Upper(Work$) STATIC
  1267.  
  1268.   '-- load SI with the address of Work$ descriptor
  1269.   SI = VARPTR(Work$)
  1270.  
  1271.   '-- assign LEN(Work$) to BX
  1272.   BX = PEEK(SI) + 256 * PEEK(SI + 1)
  1273.  
  1274.   '-- the address of the first character goes in SI
  1275.   SI = PEEK(SI + 2) + 256 * PEEK(SI + 3)
  1276.  
  1277. More:
  1278.   BX = BX - 1                'point to the end of Work$
  1279.   IF BX < 0 GOTO Exit        'no more characters to do
  1280.   AL = PEEK(SI + BX)         'get the current character
  1281.   IF AL < ASC("a") GOTO More 'skip conversion if too low
  1282.   IF AL > ASC("z") GOTO More 'or if too high
  1283.   AL = AL - 32               'convert to upper case
  1284.   POKE SI + BX, AL           'put character back in Work$
  1285.   GOTO More                  'go do it all again
  1286.  
  1287. Exit:                        'return to caller
  1288.  
  1289. END SUB
  1290.  
  1291.  
  1292. Upper in assembly language:
  1293.  
  1294. Upper Proc, Work:Word
  1295.  
  1296.   Mov  SI,Work    ;load SI with Work$'s descriptor address
  1297.   Mov  BX,[SI]    ;put LEN(Work$) into BX
  1298.   Mov  SI,[SI+2]  ;SI holds address of the first character
  1299.  
  1300. Next:
  1301.   Dec  BX         ;point to the next prior character
  1302.   Js   Exit       ;if sign is negative BX is less than 0
  1303.   Mov  AL,[BX+SI] ;put the current character into AL
  1304.   Cmp  AL,"a"     ;compare it to ASC("a")
  1305.   Jb   More       ;jump if below to More
  1306.   Cmp  AL,"z"     ;compare AL to ASC("z")
  1307.   Ja   More       ;jump if above to More
  1308.   Sub  AL,32      ;convert AL to upper case
  1309.   Mov  [BX+SI],AL ;put AL back into Work$
  1310.   Jmp  More       ;jump to More
  1311.  
  1312. Exit:
  1313.   Ret             ;return to BASIC
  1314.  
  1315. Upper Endp
  1316. End
  1317.  
  1318. What's Your Sign?
  1319.  
  1320. Notice that for expediency, these routines work backwards from the end of
  1321. the string.  There are a number of shortcuts that you can use in assembly
  1322. language, and one important one is being able to quickly test the result
  1323. of the most recent numeric operation.  If the program worked forward
  1324. through the string, it would take three lines of code to advance to the
  1325. next character, and also require saving the string length separately:
  1326.  
  1327.    Inc  BX           ;point to the next character
  1328.    Cmp  BX,Length    ;are we done yet?
  1329.    Jne  More         ;no, continue
  1330.  
  1331. Notice the use of a new form of conditional jump--Js which stands for *Jump
  1332. if Signed*.  Here the code tests the sign of the number in BX, and jumps
  1333. if it is negative.  Though I haven't mentioned this yet, a conditional jump
  1334. doesn't always have to follow a compare.  Although a comparison will set
  1335. the flags in the 8088 that indicate whether a particular condition is true,
  1336. so will several other instructions.  Some of these are Add, Sub, Dec, and
  1337. Inc, but not Mov.  So instead of having to include an explicit comparison:
  1338.  
  1339.    Dec  BX           ;decrement BX
  1340.    Cmp  BX,0         ;compare it to zero
  1341.    Jl   More         ;jump if less to More
  1342.  
  1343. All that is really needed is this:
  1344.  
  1345.    Dec  BX
  1346.    Js   More
  1347.  
  1348. The Dec instruction sets the Sign Flag automatically, just as if a separate
  1349. compare had been performed.
  1350.  
  1351.  
  1352. Conditional Jump Instructions
  1353.  
  1354. Besides Je, Jne, and Js, there are a few other forms of conditional jump
  1355. instructions you should understand.  Figure 12-6 lists all of the ones you
  1356. are likely to find useful.
  1357.  
  1358.  
  1359. Command   Meaning
  1360. ═══════   ══════════════════════════════════════
  1361.   Je      Jump if equal
  1362.   Jne     Jump if not equal
  1363.   Ja      Jump if above (unsigned basis)
  1364.   Jna     Jump if not above (unsigned basis)
  1365.   Jb      Jump if below (unsigned basis)
  1366.   Jnb     Jump if not below (unsigned basis)
  1367.   Jg      Jump if greater (signed basis)
  1368.   Jng     Jump if not greater (signed basis)
  1369.   Jl      Jump if less (signed basis)
  1370.   Jnl     Jump if not less (signed basis)
  1371.   Jc      Jump if Carry Flag is set
  1372.   Jnc     Jump if Carry Flag is clear
  1373.   Js      Jump if sign flag is set
  1374.   Jns     Jump if sign flag is not set
  1375.   Jcxz    Jump if CX is zero
  1376.  
  1377. Figure 12-6: The 8088 conditional jump instructions.
  1378.  
  1379.  
  1380. You should know that Je and Jne also have an alias command name: Jz and
  1381. Jnz.  These stand for *Jump if Zero* and *Jump if Not Zero* respectively,
  1382. and they are identical to Je and Jne.  In fact, though I didn't mention
  1383. this earlier, the Repe and Repne string repeat prefixes are sometimes
  1384. called Repz and Repnz.
  1385.    Because Je and Jz cause MASM to generate the identical machine code
  1386. bytes, they may be used interchangeably.  In some cases you may want to use
  1387. one instead of the other, depending on the logic in your program.  For
  1388. example, after comparing two values you would probably use Je or Jne to
  1389. branch if they are equal or not equal.  But after testing for a zero or
  1390. non-zero value using Or AX,AX you would probably use Jz or Jnz.  This is
  1391. really just a matter of semantics, and either version can be used with the
  1392. same results.
  1393.    Also, please understand that Jnb is not the same as Ja.  Rather, the
  1394. case of being Not Below is the same as being Above Or Equal.  In fact, MASM
  1395. recognizes Jae (Jump if Above or Equal) to mean the same thing as Jnb. 
  1396. Likewise, Jbe (Jump if Below or Equal) is the same as Jna, Jge (Jump if
  1397. Greater or Equal) is the same as Jnl, and Jle (Jump if Less or Equal) is
  1398. identical to Jng.  Again, which form of these instructions you use will
  1399. depend on how you are viewing the data and comparisons.
  1400.    Note the special form of conditional jump, Jcxz.  Jcxz stands for Jump
  1401. if CX is Zero, and it combines the effects of Cmp CX,0 and Je label into
  1402. a single fast instruction.  Jcxz is also commonly used prior to a Loop
  1403. instruction.  When you use Loop to perform an operation repeatedly, CX must
  1404. be assigned initially to the number of times the loop is to be executed. 
  1405. But if CX is zero the loop will execute 65536 times!  Thus, adding Jcxz
  1406. Exit avoids this undesirable behavior if zero was passed accidentally.
  1407.    Finally, you must be aware that a conditional jump cannot be used to
  1408. branch to a label that is more than 128 bytes earlier, or 127 bytes farther
  1409. ahead in the code.  A condition jump instruction is only two bytes, with
  1410. the first indicating the instruction and the other holding the branch
  1411. distance.  If you need to jump to a label farther away than that you must
  1412. reverse the sense of the condition, and jump to a near label that skips
  1413. over another, unconditional jump:
  1414.  
  1415.    Cmp  AX,BX             ;we want to jump to Label: if AX is greater
  1416.    Jna  NearLabel         ;so jump to NearLabel if it's NOT greater
  1417.    Jmp  Label             ;this goes to Label: which is farther away
  1418.    NearLabel:
  1419.     .
  1420.     .
  1421.  
  1422. As used here, the unconditional Jmp instruction can branch to any location
  1423. within the current code segment.  There is also a short form of Jmp, which
  1424. requires only two bytes of code instead of three.  If you are jumping
  1425. backwards in the program and the address is within 128 bytes, MASM uses the
  1426. shorter form automatically.  But if the jump is forward, you should specify
  1427. Short explicitly: Jmp Short Label.  Some non-Microsoft assemblers do not
  1428. require you to specify Short; the newest MASM version 6.x also adjusts its
  1429. generated code to avoid the extra wasted byte.
  1430.  
  1431.  
  1432. DOS STRINGS
  1433.  
  1434. When string information is passed to a DOS routine, for example when giving
  1435. a file or directory name, the string must end with a CHR$(0).  In DOS
  1436. terminology this is called an ASCIIZ string.  (Do not confuse this with a
  1437. CHR$(26) Ctrl-Z which marks the end of a file.)  Unlike BASIC, DOS does
  1438. not use string descriptors, so this is the only way DOS can tell when it
  1439. has reached the end.  By the same token, when DOS returns a string to a
  1440. calling program, it marks the end with a trailing zero byte.
  1441.    When passing a string to a DOS service from BASIC you must either
  1442. concatenate a CHR$(0) manually, or add extra code within the assembler
  1443. routine to copy the name into local storage and add a zero byte to the
  1444. copy.  From BASIC you would therefore use something like this:
  1445.  
  1446.    CALL Routine(FileName$ + CHR$(0))
  1447.  
  1448.  
  1449. BASIC FIXED-LENGTH STRINGS
  1450.  
  1451. Fixed-length strings and the string portion of a TYPE variable do not use
  1452. a string descriptor, which you might think would require a different
  1453. strategy to access them.  But whenever a fixed-length string is used as an
  1454. argument to an assembler routine or BASIC subprogram, BASIC first copies
  1455. it into a temporary conventional string, and it is the temporary string
  1456. that is passed to the routine.  When the routine returns, BASIC copies the
  1457. characters back into the original fixed-length string.  Thus, any routine
  1458. written in assembly language that expects a descriptor will work correctly,
  1459. regardless of the type of string being sent.
  1460.    Of course, this copying requires BASIC to generate many extra bytes of
  1461. assembler code for each call.  If you do not want BASIC to create a
  1462. temporary string copy from one of a fixed-length, you must first define the
  1463. string as a TYPE like this:
  1464.  
  1465.    TYPE Flen
  1466.      S AS STRING * 20
  1467.    END TYPE
  1468.    DIM FString AS FLen
  1469.  
  1470. Though this appears to be the same as defining FString as a string with a
  1471. fixed length of 20, there is an important difference: declaring it as a
  1472. TYPE tells BASIC not to make a copy.  That is, BASIC does not treat FString
  1473. as a string, as long as the ".S" portion that identifies it as a string is
  1474. not used.  Here's an example based on the FLen TYPE that was defined above:
  1475.  
  1476.    DIM FString AS FLen           'FString is a TYPE variable
  1477.    FString.S = "This is a test"  'assign the string portion
  1478.    CALL Routine(FString)         'call the routine without .S
  1479.  
  1480. Here, the address of the first character in the string is passed to the
  1481. routine, as opposed to the address of a temporary string descriptor.  We
  1482. have told BASIC to call Routine, and pass it the entire FString TYPE but
  1483. without interpreting the .S string component.  This next example does cause
  1484. BASIC to create a temporary copy:
  1485.  
  1486.    CALL Routine(FString.X)
  1487.  
  1488. The short assembly language routine that follows expects the address of a
  1489. fixed-length string with a length of 20, as opposed to the address of a
  1490. string descriptor.  The routine then copies the characters to the
  1491. upper-left corner of a color monitor.
  1492.  
  1493.  
  1494.    Push BP         ;access the stack as usual
  1495.    Mov  BP,SP
  1496.    Mov  SI,[BP+6]  ;SI points to the first character
  1497.    Mov  DI,0       ;the first address in screen memory
  1498.    Mov  AX,0B800h  ;color monitor segment when in text mode
  1499.    Mov  ES,AX      ;move into ES through AX
  1500.    Mov  CX,20      ;prepare to copy 20 characters
  1501.    Cld             ;clear the direction flag to copy forward
  1502.  
  1503. More:
  1504.    Movsb           ;copy a byte to screen memory
  1505.    Inc  DI         ;skip over the attribute byte
  1506.    Loop More       ;loop until done
  1507.    Pop  BP         ;restore BP
  1508.    Ret  2          ;return to BASIC
  1509.  
  1510.  
  1511. Recall that the color monitor segment value of 0B800h must be assigned to
  1512. ES through AX, because it is not legal to assign a segment register from
  1513. a constant.  Also, notice the way that DI is cleared to zero.  Although Mov
  1514. DI,0 indeed moves a zero into DI, this is not the most efficient way to
  1515. clear a register.  Any time a numeric value is used in a program (0 in this
  1516. case), that much extra space is needed to store the actual value as part
  1517. of the instruction.  A preferred method for clearing a register is with
  1518. the Xor instruction.  That is, Xor DI,DI gives the same result as Mov DI,0
  1519. except it is one byte shorter and slightly faster.
  1520.    When Xor is performed on any two values, only those bits that are
  1521. different are set to 1.  But since the same register is used here for both
  1522. operands, all of the result bits will be cleared to 0.  The code for using
  1523. Xor is decidedly less obvious, but you'll see Xor used this way very often
  1524. in assembly listings in magazines and books.  Another, equally efficient
  1525. way to clear a register is to subtract it from itself using Sub AX,AX.
  1526.  
  1527.  
  1528. FAR STRINGS IN BASIC PDS
  1529.  
  1530. Accessing near strings in QuickBASIC and BASIC PDS is a relatively simple
  1531. task, because both the descriptor and the string data are known to be in
  1532. near DGROUP memory.  But BASIC PDS also supports far strings, where the
  1533. data may be in a different segment.  The composition of a far string
  1534. descriptor was shown in Chapter 2; however, you do not need to manipulate
  1535. these descriptors yourself directly.
  1536.    BASIC PDS includes two routines--StringLength and StringAddress--that
  1537. do the work of locating far strings for you.  Further, because Microsoft
  1538. could change the way far strings are organized in the future, it makes the
  1539. most sense to use the routines Microsoft supplies.  If the layout of far
  1540. string descriptors changes, your program will still work as expected.
  1541.    StringLength and StringAddress expect the address of the string
  1542. descriptor, and they return the string's length and segmented address
  1543. respectively.  Note that while far string data may be in nearly any
  1544. segment, the descriptors themselves are always in DGROUP.  Also note that
  1545. these routines are not very well-behaved.  In particular, registers you may
  1546. be using are changed by the routines.  To solve this problem and also to
  1547. let you get all of the information in a single call, I have written the
  1548. StringInfo routine.  StringInfo is contained in the FAR$.ASM file on the
  1549. accompanying disk.
  1550.  
  1551. ;from an idea originally by Jay Munro
  1552. .Model Medium, Basic
  1553.   Extrn StringAddress:Proc ;these are part of PDS
  1554.   Extrn StringLength:Proc
  1555.  
  1556. .Code
  1557. StringInfo Proc Uses SI DI BX ES
  1558.  
  1559.   Pushf                    ;save the flags manually
  1560.  
  1561.   Push ES                  ;save ES for later
  1562.   Push SI                  ;pass incoming descriptor
  1563.   Call StringAddress       ;call the PDS routine
  1564.  
  1565.   Pop  ES                  ;restore ES for StringLength
  1566.   Push AX                  ;save offset and segment
  1567.   Push DX                  ;  returned by StringAddress
  1568.  
  1569.   Push SI                  ;pass incoming descriptor
  1570.   Call StringLength        ;get the length
  1571.   Mov  CX,AX               ;copy the length to CX
  1572.  
  1573.   Pop  DX                  ;retrieve the saved Segment
  1574.   Pop  AX                  ;and the address
  1575.  
  1576.   Popf                     ;restore the flags manually
  1577.   Ret                      ;restore registers and return
  1578.  
  1579. StringInfo Endp
  1580. End
  1581.  
  1582. StringInfo is called with DS:SI pointing to the string descriptor, and it
  1583. returns the length in CX and the address of the string data in DX:AX. 
  1584. Although StringInfo could be designed to return the segment in DS or ES,
  1585. it is safer to assign the segment registers yourself manually.
  1586.    Notice the Uses clause--this tells MASM that the named registers must
  1587. be preserved, and generates additional code to push those registers upon
  1588. entry to the procedure, and pop them again upon exit.
  1589.    Also notice the new Extrn directive at the beginning of the source file. 
  1590. These tell the assembler that the stated routines are not in the current
  1591. source file.  MASM then places the external name in the object file header,
  1592. with instructions to LINK to fill in the address portion of the Call.  Data
  1593. must also be declared as external if it is not in the same source file as
  1594. the routine being assembled.  When a data item is to be made available to
  1595. other modules, you must also have a corresponding Public statement in that
  1596. file for the same reason:
  1597.  
  1598.    .Model Medium, Basic
  1599.    .Data
  1600.      Public MyData
  1601.      MyData DW 12345
  1602.       .
  1603.       .
  1604.  
  1605.  
  1606. ACCESSING ARRAYS
  1607. ================
  1608.  
  1609. As you have seen, a conventional variable is passed to an assembly language
  1610. subroutine by placing its address onto the stack.  If the variable is a
  1611. string, then the address passed is that of its descriptor, and the string
  1612. data address is read from there.  Accessing array elements is only slightly
  1613. more involved, because array elements are always stored in adjacent memory
  1614. locations.  Let's look first at integer arrays.
  1615.    When BASIC encounters the statement DIM X%(100) in your program, it
  1616. allocates a contiguous block of memory 202 bytes long.  (Unless you first
  1617. used the statement OPTION BASE 1, dimensioning an array to 100 means 101
  1618. elements.)  The first two bytes in this block hold the data for X%(0), the
  1619. next two bytes hold X%(1), and so forth.  When you ask VARPTR to find
  1620. X%(0), the address it returns is the start of this block of memory.
  1621.    The address of subsequent array elements may then be easily computed
  1622. from this base address.  But with a dynamic array, the segment that holds
  1623. the array may not be the same as the segment where regular variables are
  1624. stored.  Also, huge arrays that span more than 64K require extra care when
  1625. crossing a 64K segment boundary.
  1626.    String arrays are structured in a similar fashion, in that each element
  1627. follows the previous one in memory.  For each string array element that is
  1628. dimensioned, four bytes are set aside.  These bytes comprise a table of
  1629. descriptors which contain the length and address words for each element in
  1630. the array.  But the important point is that once you know where one element
  1631. or string descriptor is located, it is easy to find all of those that are
  1632. adjacent.  Following is a QuickBASIC example that shows how to locate
  1633. Array$(15), based on the VARPTR address of Array$(0).
  1634.  
  1635.  
  1636. DIM Array$(100)
  1637. Array$(15) = "Find me"
  1638.  
  1639. Descriptor = VARPTR(Array$(0))
  1640. Descriptor = Descriptor + (4 * 15)
  1641.  
  1642. Length = PEEK(Descriptor) + 256 * PEEK(Descriptor + 1)
  1643. PRINT "Length ="; Length
  1644.  
  1645. Addr = PEEK(Descriptor + 2) + 256 * PEEK(Descriptor + 3)
  1646. PRINT "String = ";
  1647. FOR X = Addr TO Addr + Length - 1
  1648.   PRINT CHR$(PEEK(X));
  1649. NEXT
  1650.  
  1651.  
  1652. DYNAMIC ARRAYS
  1653.  
  1654. Most of the routines shown so far manipulated variables that are located
  1655. in near memory.  BASIC can store numeric, TYPE, and fixed-length string
  1656. arrays in far memory, and additional steps are needed to read from and
  1657. write to those arrays.
  1658.    When an assembly language routine receives control after a call from
  1659. BASIC, it can access your regular variables because they are in the default
  1660. data segment.  Most memory accesses assume the data is in the segment held
  1661. in the DS register.  For example, the statement Mov [BX],AX assigns the
  1662. value in AX to the memory location identified by BX within the segment held
  1663. in DS.  Likewise, Sub [DI+10],CX subtracts the value held in CX from the
  1664. memory address expressed as DI+10, where that address is again in the
  1665. default data segment.
  1666.    It is also possible to specify a segment other than the current default. 
  1667. One way is with a *segment override* command, like this:
  1668.  
  1669.    Mov ES:[BX],AX
  1670.  
  1671. Here, the segment held in ES is used instead of DS.  A segment override
  1672. adds only one byte of code, so it is quite efficient.  If you plan to
  1673. access data in a different segment many times, you can optionally set DS
  1674. to that segment.  However, it is mandatory that you reset DS to its
  1675. original value before returning to BASIC.  You must also understand that
  1676. changing DS means you no longer have direct access to DGROUP anymore.  In
  1677. that case you could use the stack segment as an override, since the stack
  1678. segment is always the same as the data segment in a BASIC program.  The
  1679. next short example shows this in context.
  1680.  
  1681.    Push DS                ;save DS
  1682.    Mov DS,FarSegment      ;now DS points to your far data
  1683.     .                     ;access that far data here
  1684.     .
  1685.    Mov AX,SS:[Variable]   ;access Variable in DGROUP
  1686.     .                     ;access more far data here
  1687.    Pop DS                 ;restore DS before returning
  1688.  
  1689. When Microsoft introduced QuickBASIC version 2.0, one of the most exciting
  1690. new features it offered was support for dynamic numeric arrays.  Unlike
  1691. QuickBASIC near strings, string arrays, and non-array variables, these
  1692. arrays are always located outside of BASIC's near 64K data segment.  This
  1693. means that an assembler routine needs some way to know both the address and
  1694. the segment for an array element that is passed to it.
  1695.    In general, routines you design that work on an entire array will be
  1696. written to expect a particular starting element.  The routine can then
  1697. assume that all of the subsequent elements lie before or after it in
  1698. memory.  Unfortunately, this does not always work unless you add extra
  1699. steps.  If you call an assembly language routine passing one element of a
  1700. far-memory dynamic array like this:
  1701.  
  1702.    CALL Routine(Array(1))
  1703.  
  1704. BASIC makes a copy of the array element into a temporary variable in near
  1705. memory, and then passes the address of that copy to the routine.  Thus,
  1706. while the routine can still receive an array element's value, it has no way
  1707. to determine its true address.  And without the address, there is no way
  1708. to get at the rest of the array.
  1709.    Since being able to pass an entire array is obviously important, BASIC
  1710. supports two options to the CALL command--SEG and BYVAL.  The SEG keyword
  1711. indicates that both the address and the segment are to be passed on the
  1712. stack, and it also tells BASIC not to make a copy of the array element. 
  1713. SEG is used with an array element (or any variable, for that matter) like
  1714. this:
  1715.  
  1716.    CALL Routine(SEG Array%(1))
  1717.  
  1718. You could also send the segment and address manually, like this:
  1719.  
  1720.    CALL Routine(BYVAL VARSEG(Array%(1)), BYVAL VARPTR(Array%(1)))
  1721.  
  1722. In both cases, BASIC first pushes the segment where the element resides
  1723. onto the stack, followed by the element's address within that segment.  By
  1724. pushing them in this order the routine can conveniently use either Lds
  1725. (Load DS) or Les (Load ES) to get both the segment and address in one
  1726. operation:
  1727.  
  1728.    Les DI,[BP+6]       ;if using manual stack addressing
  1729. or
  1730.    Les BX,[StackArg]   ;if using MASM's simplified directives
  1731.  
  1732. Les loads four bytes in one operation, placing the lower word at [BP+6]
  1733. into the named register (DI in the first example case), and the higher word
  1734. at [BP+8] into ES.  Lds works the same, except the higher word is instead
  1735. moved into DS.  Once the segment and address are loaded, you can access all
  1736. of the array elements:
  1737.  
  1738.    Push DS              ;save DS
  1739.    Lds  SI,[BP+6]       ;now DS:SI points at first element
  1740.    Mov  [SI],AX         ;assign Array%(1) from AX
  1741.    Add  SI,2            ;now SI points at the next element
  1742.    Mov  [SI],BX         ;assign Array%(2) from BX
  1743.    Pop  DS              ;restore DS
  1744.     .                   ;continue
  1745.     .
  1746.  
  1747. If Les were used instead of Lds, then an ES: override would be needed to
  1748. assign the elements.  Although you must always preserve the contents of
  1749. DS regardless of the version of BASIC, some registers need to be saved only
  1750. when using BASIC PDS far strings.  Other registers do not need to be saved
  1751. at all.  Figure 12-7 shows which registers must be preserved based on the
  1752. version of BASIC.
  1753.  
  1754.  
  1755.  QuickBASIC and       BASIC PDS
  1756. PDS near strings     far strings
  1757. ═══════════════      ══════════
  1758.       DS                 DS
  1759.       SS                 SS
  1760.       BP                 BP
  1761.       SP                 SP
  1762.                          ES
  1763.                          SI
  1764.                          DI
  1765.  
  1766. Figure 12-7: The registers that must be preserved in an assembly language
  1767. subroutine.
  1768.  
  1769.  
  1770. Besides having to save and restore the registers shown in Figure 12-7, you
  1771. must also be sure that the Direction Flag is cleared to forward before
  1772. returning to BASIC.  The Direction Flag affects the 8088 string operations,
  1773. and is by default set to forward.  You can usually ignore the direction
  1774. flag unless you set it to backwards explicitly with the Std instruction. 
  1775. In that case, you must use a corresponding Cld command.
  1776.  
  1777.  
  1778. Huge Arrays
  1779.  
  1780. A huge array is one that spans more than one 64K segment, and as you can
  1781. imagine, it requires extra steps to access all of the elements.  That is,
  1782. the assembler routine must know which elements are in what segment, and
  1783. manually load those segments as needed.  The following code fragment shows
  1784. how to walk through all of the elements in a huge integer array, and just
  1785. for the sake of the example adds each element to determine the sum of all
  1786. of them.
  1787.    A simple setup example and call syntax for this routine is as follows:
  1788.  
  1789.    REDIM Array&(1 TO 30000)
  1790.    FOR X% = 1 TO 30000
  1791.      Array&(X%) = X%
  1792.    NEXT
  1793.  
  1794.    CALL SumArray(SEG Array&(1), 30000, Sum&)
  1795.    PRINT "Sum& ="; Sum&
  1796.  
  1797.  
  1798. And here's the code for the SumArray routine:
  1799.  
  1800. .Model Medium, Basic
  1801. .Code
  1802.  
  1803. SumArray Proc Uses SI, Array:DWord, NumEls:Word, Sum:Word
  1804.  
  1805.   Push DS          ;save DS so we can restore it later
  1806.   Push SI          ;PDS far strings require saving SI too
  1807.  
  1808.   Xor  AX,AX       ;clear AX and DX which will accumulate
  1809.   Mov  DX,AX       ; the total
  1810.  
  1811.   Mov  BX,NumEls   ;get the address for NumElements%
  1812.   Mov  CX,[BX]     ;read NumElements% before changing DS
  1813.   Lds  SI,Array    ;load the address of the first element
  1814.   Jcxz Exit        ;exit if NumElements = 0
  1815.  
  1816. Do:
  1817.   Add  AX,[SI]     ;add the value of the low word
  1818.   Adc  DX,[SI+2]   ;and then add the high word
  1819.   Add  SI,4        ;point to the next array element
  1820.  
  1821.   Or   SI,SI       ;are we beyond a 32k boundary?
  1822.   Jns  More        ;no, continue
  1823.  
  1824.   Sub  SI,8000h    ;yes, subtract 32k from the address
  1825.   Mov  BX,DS       ;copy DS into BX
  1826.   Add  BX,800h     ;adjust the segment to compensate
  1827.   Mov  DS,BX       ;copy BX back into DS
  1828.  
  1829. More:
  1830.   Loop Do          ;loop until done
  1831.  
  1832. Exit:
  1833.   Pop  SI          ;restore SI for BASIC
  1834.   Pop  DS          ;restore DS and gain access to Sum&
  1835.   Mov  BX,Sum      ;get the DGROUP address for Sum&
  1836.   Mov  [BX],AX     ;assign the low word
  1837.   Mov  [BX+2],DX   ;and then the high word
  1838.  
  1839.   Ret              ;return to BASIC
  1840.  
  1841. SumArray Endp
  1842. End
  1843.  
  1844. The segment bounds checking is handled by the six lines that start with
  1845. Or SI,SI.  The idea is to see if the address is beyond 32767, subtract
  1846. 32768 if it is, and then adjust the segment to compensate.  The most direct
  1847. way would have been with Cmp SI,32767 and then Ja More, but Cmp used this
  1848. way generates three bytes of code, whereas Or creates only two bytes. 
  1849. Since Or sets the Sign flag if the number is negative (above 32767), you
  1850. can use it to know when the address adjustment is needed.
  1851.    Because it is not legal to add or subtract a segment register, DS is
  1852. first copied to BX, 800h is added to that, and the result is then copied
  1853. back to DS.  800h is used instead of 8000h (32768) because a new segment
  1854. begins every 16 bytes.  [That is, adding 800h to a segment value is the
  1855. same as adding 8000h to the address.]
  1856.    SumArray also introduces a new instruction:  Adc means Add with Carry,
  1857. and it is used to add long integer values that by definition span two
  1858. words.  When you add two registers--say, AX and BX--if the result exceeds
  1859. 65535 only the remainder is saved.  However, the Carry Flag is set to
  1860. indicate the overflow condition.  Adc takes this into account, and adds
  1861. one extra to its result if the Carry Flag is set.  Therefore, whenever two
  1862. long integers are added you'll use Add to combine the lower words, and Adc
  1863. for the high words.  Similarly, subtracting long integers requires that you
  1864. use Sub to subtract the lower words and then Sbb (Subtract with Borrow) on
  1865. the upper words.
  1866.    Although the details are hidden from you, when more than one parameter
  1867. is passed to an assembly language routine it is the last in the list that
  1868. is at [BP+6] on the stack.  The previous argument is at [BP+8], and the one
  1869. before that is at [BP+10].  Because the stack grows downward as new items
  1870. are pushed onto it, each subsequent item is at a lower address.
  1871.    Finally, in a real program this routine would probably be designed as
  1872. a function.  Using a function avoids having to pass the Sum& parameter to
  1873. receive the returned value, and helps reduce the size of the program.
  1874.  
  1875.  
  1876. ASSEMBLER FUNCTIONS
  1877. ===================
  1878.  
  1879. Designing a procedure as a function lets you return information to a
  1880. program, but without the need for an extra passed parameter.  Functions are
  1881. also useful because BASIC performs any necessary data type conversion
  1882. automatically.  For example, if you have written a function that returns
  1883. an integer value, you can freely assign the result to a single precision
  1884. variable.
  1885.    You can also test the result of a function directly using IF, display
  1886. it directly with PRINT, or pass it as a parameter to another procedure. 
  1887. Some typical examples are shown here:
  1888.  
  1889.    SingleVar! = MyFunction%
  1890.  
  1891.    IF YourFunction&(Argument%) > 1004 THEN ...
  1892.  
  1893.    PRINT HisFunction$(Any$)
  1894.  
  1895. Beginning with QuickBASIC version 4.0, functions written in assembly
  1896. language may be added to a BASIC program.  To have a function return an
  1897. integer value, simply place the value into the AX register before returning
  1898. to BASIC.  If the function is to return a long integer, both DX and AX are
  1899. used.  In that case, DX holds the higher word and AX holds the lower one.
  1900.  
  1901.  
  1902. STRING FUNCTIONS
  1903.  
  1904. String functions are only slightly more complicated to design.  A string
  1905. function also uses AX as a return value, but in this case AX holds the
  1906. address of a string descriptor you have created.  The complete short string
  1907. function that follows accepts an integer argument, and returns the string
  1908. "False" if the argument is zero or "True" if it is not.
  1909.  
  1910. ;Syntax:
  1911. ;DECLARE FUNCTION TrueFalse$(Argument%)
  1912. ;Answer$ = TrueFalse$(Argument%)
  1913.  
  1914. .Model Medium, Basic
  1915. .Data
  1916.   DescLen DW 0
  1917.   DescAdr DW 0
  1918.   True    DB "True"
  1919.   False   DB "False"
  1920.  
  1921. .Code
  1922. TrueFalse Proc, Argument:Word
  1923.  
  1924.   Mov  DescLen,4            ;assume true
  1925.   Mov  DescAdr,Offset True
  1926.  
  1927.   Mov  BX,Argument          ;get the address for Argument%
  1928.   Cmp  Word Ptr [BX],0      ;is it zero?
  1929.   Jne  Exit                 ;no, so we were right
  1930.   Inc  DescLen              ;yes, return five characters
  1931.   Mov  DescAdr,Offset False ;and the address of "False"
  1932.  
  1933. Exit:
  1934.   Mov  AX,Offset DescLen    ;show where the descriptor is
  1935.   Ret                       ;return to BASIC
  1936.  
  1937. TrueFalse Endp
  1938. End
  1939.  
  1940. Although the function is declared using a dollar sign in the name, the
  1941. actual procedure omits that.  [The dollar sign merely tells BASIC what type
  1942. of information will be returned.  It is not part of the actual procedure
  1943. name.]  TrueFalse begins by defining a string descriptor in the .Data
  1944. segment.  It is also possible to store strings and other data in the code
  1945. segment and access it with a CS: segment override.  However, data that is
  1946. returned as a function must be in DGROUP, and so must the descriptor.
  1947.    The first two statements assign the descriptor to an output string
  1948. length of four characters, and the address of the message "True".  Then,
  1949. the address of Argument is obtained from the stack, and its value is
  1950. compared to zero.  If it is not zero, then the descriptor is already
  1951. correct and the function can proceed.  Otherwise, the descriptor length is
  1952. incremented to reflect the correct length, and the address portion is
  1953. reassigned to show where the string "False" begins in memory.  In either
  1954. case, the final steps are to load AX with the address of the descriptor,
  1955. and then return to BASIC.
  1956.    MASM also lets you access data using simple arithmetic.  For example,
  1957. the descriptor could have been defined as a single pair of words with one
  1958. name, and the second word could be accessed based on the address of the
  1959. first one like this:
  1960.  
  1961.    .Data
  1962.      Descriptor DW 0, 0
  1963.      True       DB "True"
  1964.      False      DB "False"
  1965.  
  1966.    .Code
  1967.       .
  1968.       .
  1969.      Inc  Descriptor
  1970.      Mov  Descriptor+2,Offset False
  1971.       .
  1972.       .
  1973.  
  1974.  
  1975. Far String Functions
  1976.  
  1977. Far string functions require more work to write than near string functions,
  1978. because of the added overhead needed to support far strings.  Fortunately,
  1979. BASIC includes routines that simplify the task for you.  Actually, the
  1980. routines to create and assign strings have always been included; it's just
  1981. that Microsoft never documented how to do it before BASIC 7.0.  Later in
  1982. this chapter I'll show code to create strings that works with all versions
  1983. of BASIC 4.0 or later.
  1984.    The StringAssign routine expects six arguments on the stack, for the
  1985. segment, address, and length of both the source and destination strings. 
  1986. StringAssign can assign from or to any combination of fixed- and variable-
  1987. length strings.  If the length argument for either string is zero, then
  1988. StringAssign knows that the address is that of a descriptor.  Otherwise,
  1989. the address is of the data in a fixed-length string.
  1990.    Because of the added overhead of obtaining values and pushing them on
  1991. the stack, I have created a short wrapper program that does this for you. 
  1992. MakeString accepts the same arguments as StringAssign, but they are passed
  1993. using registers rather than on the stack.  Of course, calling one routine
  1994. that in turn calls another takes additional time.  But the savings in code
  1995. size when MakeString is called repeatedly will overshadow the very slight
  1996. additional delay.
  1997.    MakeString is called with DX:AX holding the segmented address of the
  1998. source string, and CX holding its fixed length.  If the source is a
  1999. conventional string, CX is set to zero to indicate that.  The destination
  2000. address is identified with DS:DI, using BX to hold the length.  Again, BX
  2001. holds zero if the destination is not a fixed-length string.
  2002.  
  2003.  
  2004. ;from an idea originally by Jay Munro
  2005. .Model Medium, Basic
  2006.   Extrn STRINGASSIGN:Proc
  2007.  
  2008. .Code
  2009. MakeString Proc Uses DS
  2010.  
  2011.   Push DX           ;push the segment of the source string
  2012.   Push AX           ;push the address of the source string
  2013.   Push CX           ;push the string length
  2014.   Push DS           ;push the segment of the destination
  2015.   Push DI           ;push the address of the destination
  2016.   Push BX           ;push the destination length
  2017.  
  2018.   Call STRINGASSIGN ;call BASIC to assign the string
  2019.   Ret
  2020.  
  2021. MakeString Endp
  2022. End
  2023.  
  2024.  
  2025. Now, with the assistance of MakeString, TrueFalse$ can be easily modified
  2026. to work with BASIC 7 far strings:
  2027.  
  2028. .Model Medium, Basic
  2029.   Extrn MakeString:Proc        ;this is in FAR$.ASM
  2030.  
  2031. .Data
  2032.   Descriptor DW 0, 0           ;the output string descriptor
  2033.   True       DB "True"
  2034.   False      DB "False"
  2035.  
  2036. .Code
  2037. TrueFalse Proc Uses ES DS SI DI, Argument:Word
  2038.  
  2039.   Mov  CX,4             ;assume true
  2040.   Mov  AX,Offset True
  2041.  
  2042.   Mov  BX,Argument      ;get the address for Argument%
  2043.   Cmp  Word Ptr [BX],0  ;is it zero?
  2044.   Jne  @F               ;no, so we were right
  2045.  
  2046.   Inc  CX               ;yes, assign five characters
  2047.   Mov  AX,Offset False  ;and use the address of "False"
  2048.  
  2049. @@:
  2050.   Mov  DX,DS                ;assign the segment and address
  2051.   Mov  DI,Offset Descriptor ;  of the destination descriptor
  2052.   Xor  BX,BX                ;assign to a descriptor
  2053.   Call MakeString           ;let MakeString do the work
  2054.  
  2055.   Mov  AX,DI            ;AX = address of output descriptor
  2056.   Ret                   ;return to BASIC
  2057.  
  2058. TrueFalse Endp
  2059. End
  2060.  
  2061. Notice the introduction of the new at-symbol (@) assembler directive.  The
  2062. at-symbol and double at-symbol label are quite useful, because they let you
  2063. avoid having to create unique label names each time you specify the target
  2064. of a jump.  As with BASIC, creating many different label names is a
  2065. nuisance, and also impinges on the assembler's working memory.  When a
  2066. label is defined using @@: as a name, you can jump forward to it using @F
  2067. or backwards using @B.  Multiple @@: labels may be used in the same
  2068. program, and @F and @B always branch to the nearest one in the stated
  2069. direction.
  2070.  
  2071.  
  2072. FLOATING POINT FUNCTIONS
  2073.  
  2074. Single and double precision functions are handled in yet another manner. 
  2075. Although a single precision value could be returned in the DX:AX register
  2076. combination, a double precision result would need four registers, which is
  2077. impractical.  Further, a floating point number is most useful to BASIC if
  2078. it is stored in a memory location, rather than in registers.
  2079.    When BASIC invokes a floating point function it adds an extra, dummy
  2080. parameter to the end of the list of arguments you pass.  If no parameters
  2081. are being used, it creates one.  This parameter is the address into which
  2082. your routine is to place the outgoing result.  Because of this added
  2083. parameter, it is essential that you account for it when returning to BASIC. 
  2084. Thus, a function without arguments must use Ret 2, a function with one
  2085. argument needs Ret 4, and so forth.  Since we're using MASM's simplified
  2086. directives, all that is needed is to create an extra parameter name.
  2087.    The short double precision function that follows squares a double
  2088. precision number much faster than using Value# ^ 2, and also shows how to
  2089. perform simple floating point math using assembly language.  You will
  2090. declare and invoke Square like this:
  2091.  
  2092.    DECLARE FUNCTION Square#(Variable#)
  2093.    Result = Square#(Variable#)
  2094.  
  2095. ;SQUARE.ASM, squares a double precision number
  2096. ;
  2097. ;WARNING: This file must be assembled using /e (emulator).
  2098.  
  2099. .Model Medium, Basic
  2100. .Code
  2101. .8087                   ;allow 8087 instructions
  2102.  
  2103. Square Proc, InValue:Word, OutValue:Word
  2104.  
  2105.   Mov  BX,InValue       ;get the address for InValue
  2106.   FLd  QWord Ptr [BX]   ;load InValue onto the 8087 stack
  2107.   FMul QWord Ptr [BX]   ;multiply InValue by itself
  2108.  
  2109.   Mov  BX,OutValue      ;get the address for OutValue
  2110.   FStp QWord Ptr [BX]   ;store the result there
  2111.   FWait                 ;wait for the 8087 to finish
  2112.  
  2113.   Mov  AX,BX            ;return DX:AX holding the full
  2114.   Mov  DX,DS            ;  address of the output value
  2115.   Ret                   ;return to BASIC
  2116.  
  2117. Square Endp
  2118. End
  2119.  
  2120. This Square function illustrates several important points.  The first is
  2121. the use of MASM's /e switch, which lets an assembly language routine share
  2122. BASIC's floating point emulator.  When a BASIC program begins, it looks to
  2123. see if an 8087 coprocessor is installed in the host PC.  If so, it uses one
  2124. set of library routines; otherwise it uses another.
  2125.    The library routines that use an 8087 simply modify the caller's code
  2126. to change the floating point interrupts that BASIC generates into actual
  2127. 8087 instructions.  It then returns to the instruction it just created and
  2128. executes it.  Although this adds to the time needed to perform a floating
  2129. point operation, the code is patched only once.  Thus, statements within
  2130. a FOR or DO loop operate very quickly after the first iteration.  This is
  2131. very much like the method used by the BRUN library described in Chapter 1.
  2132.    When no coprocessor is detected, the floating point interrupts that
  2133. BASIC generates are used to invoke routines in BASIC's floating point
  2134. software emulator.  As its name implies, an emulator imitates the behavior
  2135. of a coprocessor using assembly language commands.  A coprocessor can
  2136. perform a variety of floating point operations, including addition,
  2137. multiplication, and rounding, as well as some transcendental functions such
  2138. as logarithms and arctangents.
  2139.    When you use the /e switch, MASM adds extra information to the object
  2140. file header that tells LINK where to patch your 8087 instructions.  LINK
  2141. can then change your code to the equivalent floating point interrupts,
  2142. similar to the way BASIC patches its own code to change the interrupts to
  2143. 8087 instructions.  Therefore, when you write floating point code that will
  2144. be called from BASIC, your routine can tie into BASIC's emulator, and use
  2145. it automatically if no coprocessor is installed.
  2146.    Also, notice the .8087 directive which tells MASM not to issue an error
  2147. message when it sees those instructions.  Other, similar directives are
  2148. .80287 and .80387, and also .80286 and .80386.  These directives inform
  2149. MASM that you are intentionally using advanced commands that require these
  2150. processors, and have not made a typing error.
  2151.    The actual body of the Square function is fairly simple.  First, the
  2152. address of the incoming value is retrieved from the system stack, and then
  2153. the data at that address is loaded onto the coprocessor's stack using the
  2154. FLd (Floating point Load) instruction.  Since this is a double precision
  2155. value, QWord Ptr (Quad Word Pointer) is needed to indicate the size of the
  2156. data.  Had the incoming value been single precision, DWord Ptr (Double
  2157. Word Pointer) would be used instead.  One important feature of an 8087 or
  2158. software emulator is that a number may be converted from one numeric format
  2159. to another simply by loading it as one data type, and then saving it as
  2160. another.
  2161.    The next instruction, FMul (Floating point Multiply), multiplies the
  2162. value currently on the 8087 stack by the same address.  Since the original
  2163. value is still present, there's no need to make a new copy.  Next, the
  2164. destination address is placed into BX, and the result now on the 8087 stack
  2165. is stored there.  The trailing letter p in the FStp instruction specifies
  2166. that the value loaded earlier is to be popped from the coprocessor stack.
  2167.    A complete discussion of 8087 instructions and how the coprocessor stack
  2168. operates goes beyond what I can hope to cover here.  When in doubt about
  2169. what instruction is needed, I suggest that you code a similar sample in
  2170. BASIC, and then examine the code BASIC generates using CodeView.  There are
  2171. also several books that focus on writing floating point instructions in
  2172. assembly language.
  2173.    The last 8087 instruction is FWait, and it tells the 8088 to wait until
  2174. the coprocessor has finished, before continuing.  Because an 8087 is a true
  2175. coprocessor, it operates independently of the main 8088 CPU.  Once a value
  2176. is loaded and the 8087 is instructed to perform an operation, the 8087
  2177. returns immediately to the program that issued the instruction and
  2178. continues to process the numbers in the background.  If Square exited
  2179. immediately and BASIC read the returned value, there's a good chance that
  2180. the 8087 did not finish and the value has not yet been stored!  In that
  2181. case, whatever happened to be in memory at that time would be the value
  2182. that BASIC uses, which is obviously incorrect.
  2183.    Experienced 8087 programers know how long the various coprocessor
  2184. instructions take to complete, and with careful planning the number of
  2185. FWait commands can be kept to a minimum.  However, the code that BASIC
  2186. generates always finishes with an FWait.  Of course, there is no need to
  2187. wait when the emulator is in use.  In fact, an FWait is patched by BASIC
  2188. to do nothing (Mov AX,AX), rather than waste time invoking an empty
  2189. interrupt handler repeatedly.
  2190.    As shown, Square can be added to a Quick Library for use with either
  2191. QuickBASIC or BASIC PDS.  Unfortunately, the information link needs to
  2192. patch 8087 instructions is available only with BASIC PDS.  Therefore, the
  2193. following file is included in the libraries on the accompanying disk, to
  2194. supply the external data that LINK requires.
  2195.  
  2196.  
  2197. ;FIXUPS.ASM, deciphered by Paul Passarelli
  2198.  
  2199.   FIARQQ  Equ 0FE32h
  2200.   FJARQQ  Equ 04000h
  2201.   FICRQQ  Equ 00E32h
  2202.   FJCRQQ  Equ 0C000h
  2203.   FIDRQQ  Equ 05C32h
  2204.   FIERQQ  Equ 01632h
  2205.   FISRQQ  Equ 00632h
  2206.   FJSRQQ  Equ 08000h
  2207.   FIWRQQ  Equ 0A23Dh
  2208.  
  2209.   Public  FIARQQ
  2210.   Public  FJARQQ
  2211.   Public  FICRQQ
  2212.   Public  FJCRQQ
  2213.   Public  FIDRQQ
  2214.   Public  FIERQQ
  2215.   Public  FISRQQ
  2216.   Public  FJSRQQ
  2217.   Public  FIWRQQ
  2218. End
  2219.  
  2220.  
  2221. These values are added to the floating point instruction bytes during the
  2222. linking process, and the addition converts those statements into equivalent
  2223. BASIC floating point interrupt commands.  For example, the 8087 statement
  2224. Fld DWord Ptr [1234h] is represented in memory as the following series of
  2225. Hexadecimal bytes:
  2226.  
  2227.    9B D9 06 34 12
  2228.  
  2229. After LINK adds the value FIDRQQ (5C32h) to the first two bytes of this
  2230. command the result is:
  2231.  
  2232.    CD 35 06 34 12
  2233.  
  2234. And when disassembled back to assembler mnemonics, the CD35h displays as
  2235. Int 35h.  The three bytes that follow are always left unchanged, and they
  2236. specify the type of operation--DWord Ptr on a memory location--and the
  2237. address of that location.
  2238.  
  2239.  
  2240. Floating Point Comparisons
  2241.  
  2242. At the core of any sorting or searching routine is an appropriate
  2243. comparison function.  Previous chapters showed how to compare string data,
  2244. and as you can imagine comparing floating point values is much more
  2245. complex.  But now that you know how to tap into BASIC's floating point
  2246. routines it is almost trivial to effect a floating point comparison.  The
  2247. routines that follow let you compare either single- or double precision
  2248. values, by passing them as arguments.
  2249.  
  2250. ;COMPAREFP.ASM, compares floating point values
  2251.  
  2252. ;WARNING: This file must be assembled using /e (emulator)
  2253.  
  2254. .Model Medium, Basic
  2255.   Extrn B$FCMP:Proc   ;BASIC's FP compare routine
  2256.  
  2257. .8087                 ;allow coprocessor instructions
  2258. .Code
  2259.  
  2260. CompareSP Proc, Var1:Word, Var2:Word
  2261.  
  2262.   Mov  BX,Var2        ;get the address of Var1
  2263.   Fld  DWord Ptr [BX] ;load it onto the 8087 stack
  2264.   Mov  BX,Var1        ;same for Var2
  2265.   Fld  DWord Ptr [BX]
  2266.   FWait               ;wait until the 8087 says it's okay
  2267.   Call B$FCMP         ;compare the values, (and pop both)
  2268.  
  2269.   Mov  AX,0           ;assume they're the same
  2270.   Je   Exit           ;we were right
  2271.   Mov  AL,1           ;assume Var1 is greater
  2272.   Ja   Exit           ;we were right
  2273.   Dec  AX             ;Var1 must be less than Var2
  2274.   Dec  AX             ;decrement AX to -1
  2275.  
  2276. Exit:
  2277.   Ret                 ;return to BASIC
  2278.  
  2279. CompareSP Endp
  2280.  
  2281.  
  2282.  
  2283. CompareDP Proc, Var1:Word, Var2:Word
  2284.  
  2285.   Mov  BX,Var2        ;as above
  2286.   Fld  QWord Ptr [BX]
  2287.   Mov  BX,Var1
  2288.   Fld  QWord Ptr [BX]
  2289.   FWait
  2290.   Call B$FCMP
  2291.  
  2292.   Mov  AX,0
  2293.   Je   Exit
  2294.   Mov  AL,1
  2295.   Ja   Exit
  2296.   Dec  AX
  2297.   Dec  AX
  2298.  
  2299. Exit:
  2300.   Ret
  2301.  
  2302. CompareDP Endp
  2303. End
  2304.  
  2305. Like the Compare3 function shown in Chapter 8, CompareSP and CompareDP are
  2306. integer functions that return -1, 0, or 1 to indicate if the first value
  2307. is less than, equal to, or greater than the second.  Therefore, to use
  2308. these from BASIC you would invoke them like this:
  2309.  
  2310.    IF CompareSP%(Value1!, Value2!) = -1 THEN
  2311.      'the first value is smaller than the second
  2312.    END IF
  2313.  
  2314. And to test if the first is equal to or greater than the second you would
  2315. instead do this:
  2316.  
  2317.    IF CompareSP%(Value1!, Value2!) >= 0 THEN
  2318.      'the first value is equal or greater
  2319.    END IF
  2320.  
  2321. You can also use these functions from assembly language.  But if you do
  2322. this, I suggest a simple modification.  A comparison routine meant to be
  2323. called from another assembler routine would not generally return the result
  2324. in the registers.  Rather, it would leave the flags set appropriately for
  2325. a subsequent Ja or Jne branch.
  2326.    Fortunately, BASIC's B$FCMP routine already does this.  Therefore, you
  2327. will make a copy of the COMPAREF.ASM source file, and delete the six lines
  2328. between the call to B$FCMP and the Ret instruction.  You can also remove
  2329. the Exit: label if you like, although its presence causes no harm.  Of
  2330. course, the code itself is so simple that the best solution may be to
  2331. simply duplicate the same instructions inline in your routine.
  2332.  
  2333.  
  2334. EXPLOITING MASM'S FEATURES
  2335. ==========================
  2336.  
  2337. Each example I have shown so far introduced another useful MASM feature. 
  2338. For example, you learned how MASM lets you establish data memory with an
  2339. initial value, so you don't have to assign it explicitly.  But there are
  2340. several other features you should know about as well.  One is conditional
  2341. assembly.
  2342.  
  2343.  
  2344. CONDITIONAL ASSEMBLY
  2345.  
  2346. With conditional assembly you can specify that only certain portions of a
  2347. file are to be assembled.  This makes it easier to maintain two different
  2348. versions of a routine, for example one for near strings and one for far
  2349. strings.  If you had to create two separate copies of the source file, any
  2350. improvements or bug fixes that you add would have to be done twice.
  2351.    There are two ways that a section of code can be optionally included or
  2352. excluded.  One is to define a constant at the beginning of the source file,
  2353. and then test that constant using a form of IF and ELSE test.  Like BASIC,
  2354. MASM lets you define constant values using meaningful names.  The problem
  2355. with this method--albeit a minor one--is that you must alter the code prior
  2356. to assembling each version.  The example that follows shows how this kind
  2357. of conditional assembly is employed.
  2358.  
  2359.    MyConst = 1
  2360.     .
  2361.     .
  2362.    IF MyConst
  2363.           ;do whatever you want here
  2364.    ELSE   ;the ELSE is optional
  2365.           ;do whatever else you want here
  2366.    ENDIF
  2367.     .
  2368.     .
  2369.  
  2370. The idea is that if you want the code that follows the IF test to be
  2371. assembled, you would use a non-zero value for MyConst.  If you wanted to
  2372. create an alternate version using the code within the optional ELSE block,
  2373. you would change the value to be zero.
  2374.    You can also use IFE (If Equal to zero) to test if a constant is zero. 
  2375. And this brings up another interesting MASM feature.  There are actually
  2376. two types of constants you can define.  The constant MyConst shown above
  2377. is called a *redefinable* constant, because you can actually change its
  2378. value during the course of a program.  The other type of constant is
  2379. defined using the Equ (Equate) directive, and may not be changed:
  2380.  
  2381.    YourConst Equ 100
  2382.  
  2383. Redefinable constants are often used in repeating macros, and macros are
  2384. discussed later in this section.
  2385.    The other way to tell MASM that it is to assemble just a portion of the
  2386. file is with IFDEF.  IFDEF (If Defined) tests if a constant has been
  2387. defined at all, as apposed to comparing for a specific value.  The value
  2388. of this approach is that you can define a constant on the MASM command line
  2389. when you run it.  The first example below tells MASM to assemble the code
  2390. within the IFDEF block, and the second tells it to not to.
  2391.  
  2392.    C:\ASM\> masm program /def myconst ;
  2393.  
  2394.    C:\ASM\> masm program ;
  2395.  
  2396. Here's the portion of the routine that is being assembled conditionally:
  2397.  
  2398.    IFDEF MyConst
  2399.      ;do something optional here
  2400.    ENDIF
  2401.  
  2402. Likewise, IFNDEF (If Not Defined) tests if a constant has not been defined
  2403. when reversing the logic is more sensible to you.  MASM includes a great
  2404. number of such conditional tests, and only by reading that section of the
  2405. MASM manual will you become familiar with those that are the most useful.
  2406.  
  2407.  
  2408. COMMENT BLOCKS
  2409.  
  2410. Another useful MASM feature that I personally would love to see added to
  2411. BASIC is multi-line comment blocks.  The Comment command accepts any single
  2412. character you choose as a delimiter, and considers everything thereafter
  2413. to be comments until the same character is encountered.  Many programmers
  2414. use a vertical bar, because it is not a common character:
  2415.  
  2416.    Comment |
  2417.    This program is intended to blah blah blah, and it works
  2418.    by loading AX with blah blah blah.
  2419.    |
  2420.  
  2421. Besides avoiding the need to place an explicit semicolon on each comment
  2422. line, this also makes it easy to remark out large sections of code while
  2423. you are debugging a routine.
  2424.  
  2425.  
  2426. QUOTED STRINGS
  2427.  
  2428. Yet another useful feature is MASM's willingness to use either single or
  2429. double quotes to indicate ASCII text and individual characters.  In BASIC,
  2430. if you want to specify a double quote you must use CHR$(34)--it simply is
  2431. not legal to use """, where the quote in the middle is the character being
  2432. defined.  [With the introduction of VB/DOS triple quotes may now be used
  2433. for this purpose.]  If you need to define a double quote simply surround
  2434. it with apostrophes like this:
  2435.  
  2436.    SomeData DB '"'
  2437.    Mov  AH, '"'
  2438.  
  2439. Or you can place a single quote within double quotes like this:
  2440.  
  2441.    Add DL, "'"
  2442.  
  2443. MASM can use either convention as needed, which is a feature I personally
  2444. like a lot.
  2445.  
  2446.  
  2447. LENGTH AND ADDRESS SELF-CALCULATION
  2448.  
  2449. Whenever MASM sees the dollar sign ($) operator it interprets that to mean
  2450. *here*, or the current address.  This can be used both for data and code,
  2451. though it is more common with data as the example below illustrates.
  2452.  
  2453.    .Data
  2454.      Descriptor DW MsgLen, Address
  2455.      Message    DB "This is a message."
  2456.      Address =  Offset Message
  2457.      MsgLen  =  $ - Address
  2458.  
  2459. The expression $ - Address tells the assembler to take the current data
  2460. address, and subtract from that the address where Message begins.  This is
  2461. a very powerful concept because it frees the programmer from many tedious
  2462. calculations.  In particular, if the string contents are changed at a later
  2463. time, the new length is recalculated by MASM automatically.
  2464.  
  2465.  
  2466. DEFINING DATA STRUCTURES
  2467.  
  2468. To assist you in manipulating data structures, MASM offers the Struc
  2469. directive.  This is identical to BASIC's TYPE statement, whereby you define
  2470. the organization of a collection of related data items.  The example below
  2471. shows how to define a custom data structure using BASIC, followed by an
  2472. equivalent MASM Struc definition.
  2473.  
  2474.  
  2475. BASIC:
  2476.  
  2477.    TYPE MyType
  2478.      LastName  AS STRING * 15
  2479.      FirstName AS STRING * 12
  2480.      ZipCode   AS STRING * 5
  2481.      RecordPtr AS LONG
  2482.    END TYPE
  2483.    DIM MyVar AS MyType
  2484.  
  2485.  
  2486. MASM:
  2487.  
  2488.    Struc MyStruc
  2489.      LastName  DB 15 Dup (?)
  2490.      FirstName DB 12 Dup (?)
  2491.      ZipCode   DB  5 Dup (?)
  2492.      RecordPtr DD  ?
  2493.    MyStruc Ends
  2494.    MyVar DB Size MyStruc Dup (?)
  2495.  
  2496.  
  2497. Like BASIC, defining a structure merely establishes the number and type
  2498. of data items that will be stored; memory is not actually set aside until
  2499. you do that manually.  In BASIC, you must use DIM to establish the memory
  2500. that will hold the TYPE variable.  In assembly language you instead use DB
  2501. in conjunction with the Size directive, to set aside the appropriate number
  2502. of bytes.
  2503.    Each component of the Structure is defined using an identifying name and
  2504. a corresponding data type.  Then, whenever a structure member is referenced
  2505. in your assembler routine, MASM replaces it with a number that shows how
  2506. far into the structure that member is located.  MASM uses the same syntax
  2507. as BASIC, with a period between the data name and the structure identifier. 
  2508. Here are a few examples:
  2509.  
  2510.    Mov  AL,[BX+MyVar.LastName]   ;same as Mov AL,[BX+15]
  2511.    Les  DI,[MyVar.RecordPtr]     ;loads ES:DI from RecordPtr
  2512.  
  2513.  
  2514. MINIMIZING DGROUP USAGE
  2515. =======================
  2516.  
  2517. In many cases you will store the variables your routines need in DGROUP
  2518. using the .Data directive.  As with static subprograms and functions in
  2519. BASIC, this data will not change between subroutine calls.  But this also
  2520. means that these variables are combined into the same 64k segment that is
  2521. shared with BASIC.  When there are many variables or many different
  2522. routines each with their own variables, this can significantly reduce the
  2523. amount of near memory available to BASIC.  There are two effective
  2524. solutions to this problem.
  2525.  
  2526.  
  2527. LOCAL VARIABLES
  2528.  
  2529. One way to reduce the DGROUP impact of many variables is to place some of
  2530. them onto the system stack.  MASM lets you do this automatically with its
  2531. Local directive, or you can do it manually by subtracting the requisite
  2532. number of bytes from SP.  Of course, there is only so much room on the
  2533. stack, so this approach is most useful when there are many routines and
  2534. each has less than 1K or so of data.  Stack variables are also useful when
  2535. programming for OS/2 or Windows.  These operating systems require that all
  2536. of your procedures be reentrant so static variables cannot be used.
  2537.    The example below creates room for fifty words of local storage on the
  2538. stack, and then clears the variables to zero.
  2539.  
  2540.    Routine Proc Uses ES DI, Param1:Word, Param2:Word
  2541.      Sub  SP,100         ;50 words = 100 bytes
  2542.      Push SS             ;assign ES from SS
  2543.      Pop  ES
  2544.      Mov  DI,SP          ;point DI to the start of storage
  2545.      Xor  AX,AX          ;fill with zeros
  2546.      Mov  CX,50          ;clear fifty words
  2547.      Rep  Stosw          ;store AX CX times at ES:[DI]
  2548.       .                  ;the routine continues
  2549.       .
  2550.      Add  SP,100         ;restore SP to what it had been
  2551.      Ret                 ;return to BASIC
  2552.    Routine Endp
  2553.  
  2554. MASM can also do this automatically for you using Local like this:
  2555.  
  2556.    Routine Proc Uses ES DI, Param1:Word, Param2:Word
  2557.      Local Buffer [100]:Byte
  2558.      Lea  DI,Buffer      ;clear the stack variables here
  2559.       .                  ;the routine continues
  2560.       .
  2561.      Ret                 ;return to BASIC
  2562.    Routine Endp
  2563.  
  2564. As you can see, Local lets you refer to the start of the local stack data
  2565. area by name.  Notice how Lea is required here, because the address of
  2566. Buffer is expressed as an offset from BP.  That is, MASM translates the
  2567. Lea instruction to Lea DI,[BP-100].  You cannot use Mov DI,Offset Buffer
  2568. because Buffer's address (which is based on the current setting of the
  2569. stack pointer) is not known when the routine is assembled or linked.
  2570.    In this case only one local block is defined, so you could also use Mov
  2571. DI,SP to set DI to point to the start of the data.  It is not strictly
  2572. necessary to clear the stack space before using it, but it is important to
  2573. understand that whatever junk happened to be in memory at that time will
  2574. still be there after using Local.
  2575.    It is also important to be aware of a number of bugs with the Local
  2576. directive.  I have found that limiting the use of Local to a single set of
  2577. data as shown here is safe with all MASM versions through 5.1.  Using
  2578. multiple Local directives defined with data structures can result in the
  2579. wrong part of the stack being written to when a structure member is
  2580. accessed by name.
  2581.  
  2582.  
  2583. STORING DATA IN THE CODE SEGMENT
  2584.  
  2585. Another time-honored technique for conserving DGROUP memory is to place
  2586. selected variables into the code segment.  In most cases storing data for
  2587. a routine in the code segment will make your programs slightly larger and
  2588. slower, because of the need for an added CS: segment override.  But when
  2589. large amounts of data must be accommodated, this can be very valuable
  2590. indeed.  One advantage to using the code segment is that you can establish
  2591. initial values for the data, which is not possible when using the stack.
  2592.    As an example of this technique, I have written a string function called
  2593. Message$ that stores a series of messages in the code segment.  In this
  2594. case only a single CS: segment override is needed, so the impact of using
  2595. the code segment for data is insignificant.  Message$ is designed to be
  2596. declared and invoked as follows:
  2597.  
  2598.    DECLARE FUNCTION Message$(BYVAL MsgNumber%)
  2599.    Result$ = Message$(AnyInt%)
  2600.  
  2601. Message$ is table driven, which makes it simple to modify the routine to
  2602. change or add messages without having to make any changes to the function's
  2603. structure.  As shown here, Message$ is designed to return the name of a
  2604. weekday, given a value between one and seven.  You can easily modify it to
  2605. return other strings of nearly any length.
  2606.  
  2607. .Model Medium, Basic
  2608.   Extrn B$ASSN:Proc         ;BASIC's assignment routine
  2609.  
  2610. .Data
  2611.   Descriptor DD 0           ;the output string descriptor
  2612.   Null$      DD 0           ;use this to return a null
  2613.                             ;  (needed for BASIC PDS only,
  2614. .Code                       ;  but okay with QuickBASIC)
  2615.  
  2616. Message Proc Uses SI, MsgNumber:Word
  2617.  
  2618.   Mov  SI,Offset Messages   ;point to start of messages
  2619.   Xor  AX,AX                ;assume an invalid value
  2620.  
  2621.   Mov  CX,MsgNumber         ;load the message number
  2622.   Cmp  CX,NumMsg            ;does this message exist?
  2623.   Ja   Null                 ;no, return a null string
  2624.   Jcxz Null                 ;ditto if they pass a zero
  2625.  
  2626. Do:                         ;walk through the messages
  2627.   Lods Word Ptr CS:0        ;load and skip over this message's length
  2628.   Dec  CX                   ;show that we read another
  2629.   Jz   Done                 ;this is the one we want
  2630.  
  2631.   Add  SI,AX                ;skip over the message text
  2632.   Jmp  Short Do             ;continue until we're there
  2633.  
  2634. Done:
  2635.   Or   AX,AX                ;are we returning a null?
  2636.   Jz   Null                 ;yes, handle that differently
  2637.   Push CS                   ;no, pass the source segment
  2638.  
  2639. Done2:
  2640.   Push SI                   ;and the source address
  2641.   Push AX                   ;and the source length
  2642.  
  2643.   Push DS                   ;pass the destination segment
  2644.   Mov  AX,Offset Descriptor ;and the destination address
  2645.   Push AX
  2646.   Xor  AX,AX                ;0 means assign a descriptor
  2647.   Push AX                   ;pass that as well
  2648.  
  2649.   Call B$ASSN               ;let B$ASSN do the dirty work
  2650.   Mov  AX,Offset Descriptor ;show where the output is
  2651.   Ret                       ;return to BASIC
  2652.  
  2653. Null:
  2654.   Push DS                   ;pass the address of Null$
  2655.   Mov  SI,Offset Null$
  2656.   Jmp  Short Done2
  2657.  
  2658. Message Endp
  2659.  
  2660.  
  2661. ;----- DefMsg macro that defines messages
  2662. DefMsg Macro Message
  2663.   LOCAL MsgStart, MsgEnd    ;;local address labels
  2664.   NumMsg = NumMsg + 1       ;;show we made another one
  2665.   IFB <Message>             ;;if no text is defined
  2666.     DW 0                    ;;just create an empty zero
  2667.   ELSE                      ;;else create the message
  2668.     DW MsgEnd - MsgStart    ;;first write the length
  2669.     MsgStart:               ;;identify the starting address
  2670.       DB Message            ;;define the message text
  2671.     MsgEnd Label Byte       ;;this marks the end
  2672.   ENDIF
  2673. Endm
  2674.  
  2675.  
  2676. Messages Label Byte         ;the messages begin here
  2677. NumMsg = 0                  ;tracks number of messages
  2678.                             ;DO NOT MOVE this constant
  2679. DefMsg "Sunday"
  2680. DefMsg "Monday"
  2681. DefMsg "Tuesday"
  2682. DefMsg "Wednesday"
  2683. DefMsg "Thursday"
  2684. DefMsg "Friday"
  2685. DefMsg "Saturday"
  2686. End
  2687.  
  2688. After declaring BASIC's B$ASSN routine as being external, Message$ defines
  2689. two string descriptors in the Data segment.  The first is used for the
  2690. function output when returning a normal message, and the second is used
  2691. only when returning a null string.  In truth, the need for a separate
  2692. output descriptor and the slight added steps to detect the special case of
  2693. a null output string is needed only with BASIC PDS far strings.  And this
  2694. brings up an important point.
  2695.    It is impossible to write one assembly language subroutine that can work
  2696. with both QuickBASIC and BASIC PDS far strings using the normal, documented
  2697. methods.  To create a string function for use with QuickBASIC and PDS near
  2698. strings, you define and fill in a string descriptor in DGROUP, and assign
  2699. its address in AX before returning to BASIC.  And to return a far string
  2700. as a function for PDS requires calling the internal STRINGASSIGN routine
  2701. that Microsoft provides with PDS.  STRINGASSIGN works with both near and
  2702. far strings in PDS, but is not available in QuickBASIC.
  2703.    The trick is to use the *undocumented* name B$ASSN, which is really the
  2704. same thing as STRINGASSIGN.  The big difference, though, is that B$ASSN is
  2705. available in all versions of BASIC 4.0 and later.  When near strings are
  2706. used the B$ASSN routine is extracted from the near strings library.  When
  2707. linking with far strings a different version is used, extracted by LINK
  2708. from the far strings library.  This is a powerful concept to be sure, and
  2709. one we will use again for other examples later on in this chapter.
  2710.    Message$ begins by loading SI with the starting address of a table of
  2711. messages.  These messages are located at the end of the source file in the
  2712. code segment, and each is preceded with the length of the text.  Although
  2713. it may not be obvious from looking at the source listing, the message data
  2714. is actually structured like this:
  2715.  
  2716.    DW 6
  2717.    DB "Sunday"
  2718.    DW 6
  2719.    DB "Monday"
  2720.     .
  2721.     .
  2722.  
  2723. Next, AX is cleared to zero just in case the incoming string number is
  2724. illegal.  Later in the program AX holds the length of the output string;
  2725. clearing it here simply makes the program's logic more direct.
  2726.    CX is then loaded with the message number the caller asked for.  If CX
  2727. is either higher than the available number of messages or zero, the program
  2728. jumps to the code that returns a null string.  Otherwise, a small loop is
  2729. entered that walks through each message, decrementing CX as it goes.  When
  2730. CX reaches zero, SI is pointing at the correct message and AX is holding
  2731. its length.  Otherwise, the current length is added to SI, thus skipping
  2732. over that data.
  2733.    Notice the unusual form of the Lodsw statement, to allow it to work with
  2734. a CS: override.  MASM has a number of quirks that are less than intuitive,
  2735. and this is but one of them.  Normally you would use either Lodsb or Lodsw,
  2736. to indicate loading either a byte into AL or a word into AX.  But when you
  2737. use a segment override MASM requires omitting the "b" or "w" Lods suffix,
  2738. and you must state Byte Ptr or Word Ptr explicitly.  Then, a dummy argument
  2739. must be placed after the override colon.
  2740.  
  2741.  
  2742. MASM MACROS
  2743.  
  2744. The last new feature this listing introduces is the use of macros.  The
  2745. most basic use of MASM macros is to define a block of code once, and then
  2746. repeat it multiple times with a single statement.  This is not unlike
  2747. keyboard macro programs such as Borland's SuperKey, that let you assign a
  2748. string of text to a single key.  For example, you could press Alt-S and
  2749. SuperKey will type "Very truly yours", five Enter keys, and then your name.
  2750.    MASM macros also offer many other interesting and useful capabilities,
  2751. including the ability to accept arguments.  [I should mention that the main
  2752. point of the DefMsg macro is to make this function easy to modify, so you
  2753. can create other, similar string functions from this same routine.]  Before
  2754. attempting to explain the DefMsg (Define Message) macro I designed for use
  2755. with Message$, let's consider some macro basics.
  2756.    Say, for example, you find that a particular routine needs to push the
  2757. same five registers many times during the course of a procedure.  To
  2758. simplify this task you could define a macro--perhaps named PushRegs--that
  2759. performs the code sequence for you.  Such a macro definition would look
  2760. like this:
  2761.  
  2762.    PushRegs Macro
  2763.      Push AX
  2764.      Push BX
  2765.      Push SI
  2766.      Push DS
  2767.      Push ES
  2768.    PushRegs Endm
  2769.  
  2770. Now, each time you want to execute this series of instructions you would
  2771. simply use the command PushRegs.  Please understand that a macro is not the
  2772. same as a called subroutine.  The assembler still places each Push command
  2773. in sequence into your source code each time the macro is invoked.  But a
  2774. simple macro like this can reduce the amount of typing you must do, and
  2775. minimize errors such as pushing registers in the wrong order.  And in some
  2776. cases Macros also make your code easier to read.
  2777.    As I mentioned, a MASM macro can accept arguments, and it can even be
  2778. designed to accept a varying number of them.  If you need to push three
  2779. registers but which ones may change, you would define PushRegs like this:
  2780.  
  2781.    PushRegs Macro Reg1, Reg2, Reg3
  2782.      Push Reg1
  2783.      Push Reg2
  2784.      Push Reg3
  2785.    Endm
  2786.  
  2787. Then to push AX, SI, and DI you would invoke PushRegs as follows:
  2788.  
  2789.    PushRegs AX, SI, DI
  2790.  
  2791. Of course, a corresponding PopRegs macro would be defined similarly.  Once
  2792. a macro has been defined you can pass any legal argument to it.  For
  2793. example, you could also use this:
  2794.  
  2795.    PushRegs AX, Word Ptr [BP-20], IntVar
  2796.  
  2797. Here, you are pushing AX, the word 20 bytes below where BP points to on
  2798. the stack, and the integer variable named IntVar.
  2799.    A useful enhancement to this macro would let you pass it a varying
  2800. number of parameters.  The PushM macro that follows accepts any number of
  2801. arguments (up to eight), and pushes each in sequence.
  2802.  
  2803.    PushM Macro A,B,C,D,E,F,G,H     ;;add more place-holders to suit
  2804.      IRP CurArg, <A,B,C,D,E,F,G,H> ;;repeat for each argument
  2805.        IFNB <CurArg>               ;;if this arg is not blank
  2806.          Push CurArg               ;;push it
  2807.        ENDIF
  2808.      Endm                          ;;end of repeat block
  2809.    Endm                            ;;end of this macro
  2810.  
  2811. From this you can create a complementary PopM macro by changing the name,
  2812. and also changing the Push instruction to Pop.
  2813.    The IRP command works much like a FOR/NEXT loop in BASIC, and tells MASM
  2814. to repeat the following statements for each argument that was given.  IFNB
  2815. (If Not Blank) then tests each argument to see if it was in fact present
  2816. in the incoming list of parameters.  In this case, CurArg assumes the name
  2817. of the argument, and the Push instruction is expanded to specify that name.
  2818.    There is no disputing that the syntax of a MASM macro is confusing at
  2819. best.  Having to enclose some arguments in angle brackets but not others
  2820. requires frequent visits to the MASM manual.  Further, a MASM macro is
  2821. virtually impossible to debug.  If you write a macro incorrectly or create
  2822. a syntax error, MASM reports an error at the line where the macro was
  2823. invoked, rather than at the line containing the error in the macro.  It is
  2824. not uncommon to receive a number of errors all pointing to the same source
  2825. line, with no indication whatsoever where the error really is.
  2826.    Now consider how the DefMsg macro operates.  DefMsg begins by defining
  2827. a single incoming parameter named Message.  Two local labels--MsgStart and
  2828. MsgEnd--are defined, and these are needed so MASM can calculate the length
  2829. of the messages.  Although labels within a macro do not have to be declared
  2830. as local, you would get an error if the macro were used more than once. 
  2831. Like BASIC, the assembler requires that each label have a unique name.  By
  2832. using local labels MASM generates a new, unique internal name for each
  2833. macro invocation, instead of the actual label name given.
  2834.    The next statement increments a MASM variable named NumMsg.  To avoid
  2835. an error caused by calling Message$ with an invalid message number, it
  2836. compares the number you pass to the number of messages that are defined. 
  2837. This test occurs in the fourth line of the procedure, at the Cmp CX,NumMsg
  2838. statement.  NumMsg is a constant, except it may be redefined within the
  2839. routine.  (When a constant is assigned using the word Equate, its value
  2840. may not be changed by either your source code or by a macro.)  But when a
  2841. variable is defined using an equals sign (=), MASM allows it to be altered
  2842. as it assembles your program.  Understand that the resulting number is
  2843. added to your program as a constant.  However, its value can be changed
  2844. during the course of assembly.  Therefore, each time DefMsg is invoked, it
  2845. increments NumMsg.  MASM places the final value into the Cmp instruction,
  2846. as if you had defined it using a fixed known value.
  2847.    The IFB (If Blank) test checks to see if DefMsg was given a parameter
  2848. when it was invoked.  In most cases you will probably want to define a
  2849. series of consecutive messages.  As it is used here, seven different day
  2850. names are returned in sequence.  But there may be times when you want to
  2851. leave a particular message number blank.  For example, you could create a
  2852. series of messages that correspond to BASIC's error numbers.  BASIC file
  2853. error numbers range from 50 through 76, but there are no messages numbers
  2854. 60, 65, or 66.  You could therefore leave those blank, and invoke a
  2855. modified copy of Message$ like this:
  2856.  
  2857.    CALL DOSMessage$(51 - ERR)
  2858.  
  2859. When DefMsg is used with no argument, it merely creates a zero word at
  2860. that point in the code segment.  Otherwise, the length of the message is
  2861. stored, followed by the message text.  The statement DW MsgEnd - MsgStart
  2862. is replaced with the difference between the addresses, which MASM
  2863. calculates for you.  This is similar to the earlier example that showed how
  2864. a dollar sign ($) can simplify defining strings that may change.
  2865.    The last macro I will describe here is Rept, which means "Repeat the
  2866. following statements a given number of times".  In the simplest sense, Rept
  2867. could be used to generate a series of the same instructions:
  2868.  
  2869.    Rept 100
  2870.      Xor  AX,AX
  2871.      Push AX
  2872.      Call SomeProc
  2873.    Endm
  2874.  
  2875. A Rept macro is not invoked by name; rather, it is added inline to a
  2876. program (or included within a macro that is called by name).  In most cases
  2877. you would use a coding loop to repeat a block of code, since a Rept macro
  2878. actually generates the same code repeatedly in the program.  But there are
  2879. situations where timing is very critical, and a loop is always somewhat
  2880. slower than a sequence of inline instructions.
  2881.    Another good use for Rept is in conjunction with redefinable equates,
  2882. such as this example which defines the letters of the alphabet:
  2883.  
  2884.    Alphabet:
  2885.    Char = 0
  2886.    Rept 26                ;;do this 26 times
  2887.      DB "A" + Char        ;;define ASC("A") + Char
  2888.      Char = Char + 1      ;;increment Char
  2889.    Endm
  2890.  
  2891. Although the MASM manual states that you must use double semicolons for
  2892. remarks within a macro as shown here, I have used a single semicolon
  2893. without problems.
  2894.    There are other macro commands and features I will not describe here,
  2895. because I have not found them to be particularly useful.  However, macros
  2896. can be recursive, multiple macros may be nested, and even redefined on the
  2897. fly.  I urge you to refer to the documentation that Microsoft provides for
  2898. more information on those advanced features.
  2899.  
  2900.  
  2901. SEGMENT NAMING
  2902. ==============
  2903.  
  2904. Aside from the short PrtSc example shown earlier in this chapter, we have
  2905. relied upon MASM's simplified segmentation directives to spare us from the
  2906. nuisance of defining and naming segments.  Indeed, when writing routines
  2907. that will be added to BASIC it is rarely necessary to do this manually, so
  2908. why bother?
  2909.    One place where naming segments explicitly is useful is when you have
  2910. many internal procedures that are never called from BASIC directly.  If,
  2911. for clarity and organization reasons, you decide to store those routines
  2912. in different files, you still may want to access the routines using near
  2913. calls.  Since a near call is two bytes shorter than a far call and also
  2914. operates slightly faster, this can make a difference when there are many
  2915. Call commands within the routines.
  2916.    As LINK pulls all of the various pieces of your program together from
  2917. separate object and library files, it reads the segment names and combines
  2918. those with the same name.  Thus, a routine in one source file can call a
  2919. routine in a different file, and LINK will place both routines into the
  2920. same segment if they use the same segment name.  This is of course needed
  2921. to ensure that the called routine is reachable by the caller (within 64K).
  2922.    All of the standard segment names that Microsoft recommends are listed
  2923. in the MASM manual, along with instructions for creating your own names. 
  2924. Therefore, I won't belabor that here.
  2925.  
  2926.  
  2927. ACCESSING BASIC INTERNALS
  2928. =========================
  2929.  
  2930. In preceding sections you learned that it is possible--even desireable--
  2931. to call BASIC's internally routines directly.  Besides those that have
  2932. already been described, there are several other useful routines that can
  2933. be accessed from assembly language.  One of these is B_ONEXIT, which lets
  2934. you tap into BASIC's termination procedure.
  2935.    When a BASIC program ends by running out of statements, or by using END,
  2936. STOP, or SYSTEM, BASIC makes a call to a central routine that in turn tells
  2937. DOS to end the program.  If a fatal error occurs and there is no ON ERROR
  2938. handler, BASIC also calls a routine that prints an error message.  B_ONEXIT
  2939. lets you tell BASIC the segment and address of a routine you want called
  2940. as part of the termination process.  B_ONEXIT is supported only in
  2941. QuickBASIC version 4.5 and BASIC PDS.
  2942.    One reason you might want to use B_ONEXIT is to ensure that interrupts
  2943. taken over by your assembler routine are restored properly.  Taking over
  2944. interrupts will be described later in the section "Handling Interrupts." 
  2945. Here's a program fragment showing how B_ONEXIT is set up and called:
  2946.  
  2947.  
  2948. Extrn B_ONEXIT:Proc     ;declare B_ONEXIT as external
  2949. Push CS                 ;pass your code segment
  2950. Mov  AX,Offset TermProc ;and the address of the routine
  2951. Push AX                 ;  that is to be called
  2952. Call B_ONEXIT           ;register it with B_ONEXIT
  2953.   .
  2954.   .
  2955.  
  2956. TermProc Proc           ;this is the routine to be called
  2957.   .                     ;do whatever you need to here
  2958.   .
  2959.   Ret                   ;don't forget to return!
  2960. TermProc Endp
  2961.  
  2962.  
  2963. BASIC's INTERNAL DATA
  2964.  
  2965. There are two internal variables BASIC maintains that you will find useful. 
  2966. One is the current DEF SEG setting, and it is stored in the integer
  2967. variable named B$SEG.  The other is the current color value that is used
  2968. by PRINT and CLS.  The foreground and background colors are stored combined
  2969. in a single word named B$FBColors.  The reason these are useful is because
  2970. you may want to change and then restore them from inside a BASIC
  2971. subprogram.  Much of the benefit of reusable programming is lost if you
  2972. cannot put things back to the way they were originally.
  2973.    For example, if you have written a BASIC routine that prints an error
  2974. message in bright red at the bottom of the screen, you will need to use a
  2975. subsequent COLOR command to put the color back to what it had been.  But
  2976. what color do you use?  The same holds true for a routine that changes the
  2977. current DEF SEG setting, perhaps before loading or saving a file using
  2978. BLOAD or BSAVE.  If you cannot return that to its original value, extra
  2979. work is needed in the main program each time the routine is used.
  2980.    Access to B$SEG requires a single assembler instruction, as shown in the
  2981. complete GetSeg function shown following.  Declare and use GetSeg like
  2982. this:
  2983.  
  2984.    DECLARE FUNCTION GetSeg%()
  2985.    SavedSeg = GetSeg%
  2986.     .
  2987.     .
  2988.    DEF SEG = SavedSeg
  2989.  
  2990.  
  2991. ;GETSEG.ASM
  2992. .Model Medium, Basic
  2993. .Data
  2994.   Extrn B$Seg:Word
  2995.  
  2996. .Code
  2997. GetSeg Proc
  2998.  
  2999.   Mov  AX,B$Seg   ;load the value from B$Seg
  3000.   Ret             ;return with the function output in AX
  3001.  
  3002. GetSeg Endp
  3003. End
  3004.  
  3005.  
  3006. Because BASIC combines its colors into a single word, a few extra steps
  3007. are needed to separate them.  Call GetColor like this:
  3008.  
  3009.    CALL GetColor(FG%, BG%)
  3010.  
  3011. FG% and BG% are returned to you holding the current foreground and
  3012. background color values.  Here's how GetColor works:
  3013.  
  3014.  
  3015. ;GETCOLOR.ASM
  3016. .Model Medium, Basic
  3017. .Data
  3018.   Extrn B$FBColors:Word
  3019.  
  3020. .Code
  3021.  
  3022. GetColor Proc, FG:Word, BG:Word
  3023.  
  3024.   Mov  DX,B$FBColors    ;load the combined colors
  3025.   Mov  AL,DL            ;copy the foreground portion
  3026.   Cbw                   ;convert it to a full word
  3027.   Mov  BX,FG            ;get the address for FG%
  3028.   Mov  [BX],AX          ;assign FG%
  3029.   Mov  AL,DH            ;load the background portion
  3030.   Mov  BX,BG            ;get the address for BG%
  3031.   Mov  [BX],AX          ;assign BG%
  3032.   Ret                   ;return to BASIC
  3033.  
  3034. GetColor Endp
  3035. End
  3036.  
  3037.  
  3038. One unfortunate problem is that GetColor cannot be used in the editing
  3039. environment.  When BASIC compiles a PEEK or POKE statement, it generates
  3040. inline code that loads ES with the segment from B$SEG, and then reads or
  3041. writes the data at the specified address.  Therefore, the current segment
  3042. must be available to BASIC routines that use PEEK or POKE in a Quick
  3043. Library.  But the color values are accessed only by routines in BASIC's
  3044. runtime library, so the information is not made available to procedures in
  3045. a Quick Library.  Because of this issue, the GetColors routine is provided
  3046. on the accompanying disk only in the BASIC.LIB and BASIC7.LIB linking
  3047. libraries.
  3048.    There are several other internal data items you may want to know about,
  3049. and one that I have found useful is called __osversion.  This byte holds
  3050. the major DOS version number; for example, if DOS 3.x is running then
  3051. __osversion will hold the value 3.  Even though it is trivial to query DOS
  3052. for the number, why bother since you can get it this way with a single Mov.
  3053.  
  3054.  
  3055. BASIC's INTERNAL ROUTINES
  3056.  
  3057. Besides the procedures and internal data I have described previously, there
  3058. are many others you will no doubt find useful.  You can, for example, call
  3059. SETMEM prior to claiming memory from DOS.  And although the B$ASSN routine
  3060. can assign any type of data from any other type including strings, a
  3061. simplified version is also present to assign to and from conventional
  3062. strings only.
  3063.    As you have seen, the beauty of using BASIC's own routines is that
  3064. identical code can be used for both near and far strings.  In either case,
  3065. the string descriptors are known to reside in DGROUP, and the internal
  3066. routines are designed to operate on those descriptors.  You don't even have
  3067. to know which of the string libraries (near or far) is being used.
  3068.    There are also several math routines that can be accessed directly,
  3069. including those that multiply, divide, and compare long integers.  Even if
  3070. you know how to do that, it's always easier to call BASIC's routines.  This
  3071. result in less code as well.  And if you need to read the current cursor
  3072. position, you can access CSRLIN and POS(0) directly.  In some cases, you
  3073. can't read that information from the BIOS, so calling BASIC is the only
  3074. reliable way to get it.
  3075.    The following section documents the BASIC internal routines that I have
  3076. found useful when called from assembly language.  I have purposely omitted
  3077. routines that handle BASIC commands such as PRINT, INKEY, GET, and PUT. 
  3078. Even though several of these were described throughout the course of this
  3079. book, they have little relevance within a called assembler routine.
  3080.    BASIC's internal services that follow are listed in alphabetical order,
  3081. based on their call names.  Be sure to declare them as external procedures
  3082. in your routine's source code.
  3083.  
  3084.  
  3085. B$CPI4: Compare Two Long Integers
  3086.  
  3087. B$CPI4 expects two long integer arguments to be placed onto the stack by
  3088. value, and it returns the result of its comparison in the Flags register. 
  3089. For example, to see if Var1 is greater than Var2 you'd use code like this:
  3090.  
  3091.    Push Word Ptr [Var1+2]   ;first push Var1's high word
  3092.    Push Word Ptr [Var1]     ;and then its low word
  3093.    Push Word Ptr [Var2+2]   ;next do the same for Var2
  3094.    Push Word Ptr [Var2]
  3095.    Call B$CPI4              ;compare them
  3096.    Jg   Label               ;Var1 is indeed greater
  3097.  
  3098. Remember that long integers are compared by BASIC on a signed basis, so
  3099. you should use Jg or Jl rather than Ja or Jb.  The letters CPI4 stand for
  3100. Compare Integer 4 bytes.
  3101.  
  3102.  
  3103. B$CSRL: CSRLIN Function
  3104.  
  3105. B$CSRL is called with no arguments, and it returns BASIC's current row in
  3106. AX as follows:
  3107.  
  3108.    Call B$CSRL
  3109.     .                       ;do what you want with AX
  3110.  
  3111.  
  3112. B$DVI4: Divide Two Long Integers
  3113.  
  3114. Like B$CPI4, B$DVI4 (Divide Integer 4 bytes) expects the incoming integer
  3115. arguments to be passed by value on the stack.  The result is then returned
  3116. in DX:AX as a long integer:
  3117.  
  3118.    Push Word Ptr [Var2+2]   ;always push the high word first
  3119.    Push Word Ptr [Var2]     ;then the low word
  3120.    Push Word Ptr [Var1+2]   ;ditto for Var2
  3121.    Push Word Ptr [Var1]
  3122.    Call B$DVI4              ;divide them
  3123.     .                       ;now DX:AX holds Var1 \ Var2
  3124.  
  3125. Notice that with B$DVI4, the divisor is pushed first onto the stack,
  3126. followed by the dividend.
  3127.  
  3128.  
  3129. B$FPOS: POS(0) Function
  3130.  
  3131. Even though the argument passed to BASIC's POS(0) is ignored, it is still
  3132. expected mainly for historical reasons.  Therefore, you must push
  3133. something--anything--onto the stack before calling B$FPOS:
  3134.  
  3135.    Push AX
  3136.    Call B$FPOS
  3137.     .                       ;now AX holds the column
  3138.  
  3139.  
  3140. As with all of BASIC's functions that return an integer, B$FPOS returns
  3141. the current column in AX.  The leading F in FPOS stands for Function.
  3142.  
  3143.  
  3144. B$FRI2: FRE() Function
  3145.  
  3146. B$FRI2 (Free Integer 2 bytes) requires an incoming integer argument by
  3147. value on the stack, and for safety you should use this for the -1 and -2
  3148. variations only.
  3149.    Using -1 reports the total amount of memory that is available to BASIC,
  3150. so you might use this before calling SETMEM to release memory for your own
  3151. uses.  Although B$FRI2 uses an integer for an argument, it returns a long
  3152. integer in DX:AX.  You can also use an argument of -2 to see how much stack
  3153. space is available:
  3154.  
  3155.    Mov  AX,-2
  3156.    Push AX
  3157.    Call B$FRI2
  3158.    .              ;now DX:AX holds the available stack space
  3159.  
  3160.  
  3161. B$RDIM: REDIM Statement
  3162.  
  3163. In most cases you will probably not find the ability to call REDIM directly
  3164. very valuable.  One notable exception is explained later in the section
  3165. entitled "Reading the Array Descriptor," where I show how to size and then
  3166. load a string array with all of the files that match a given search
  3167. specification.
  3168.    B$RDIM is fairly complicated to set up and call, because it accepts a
  3169. varying number of parameters.  This is needed because BASIC accepts a
  3170. variable number of dimensions, and the same routine is used for all cases. 
  3171. The following example shows how to prepare and call this routine when
  3172. resizing a one-dimensional array.
  3173.  
  3174.    Mov  AX,LBound           ;first pass the lower bound value
  3175.    Push AX
  3176.    Mov  AX,UBound           ;then pass the upper bound
  3177.    Push AX
  3178.    Mov  AX,ElementLength    ;next the length of each element
  3179.    Push AX
  3180.    Mov  AX,Features         ;see the accompanying text for
  3181.    Push AX                  ;  information on these two items
  3182.    Mov  AX,Offset ArrayDescriptor
  3183.    Push AX
  3184.    Call B$RDIM              ;call REDIM to do it
  3185.  
  3186. Chapter 2 described the array descriptor in detail, including the Features
  3187. word.  However, you must not use REDIM to create a new array where none
  3188. existed before.  Instead, you will read the current features from the
  3189. existing array descriptor, and pass the same values on again to B$RDIM. 
  3190. This will be shown in context momentarily.
  3191.  
  3192.  
  3193. B$STDL: String Delete
  3194.  
  3195. You can call B$STDL to delete a string or string array element, and it
  3196. requires less code than assigning the string from another, null string. 
  3197. The single argument is the address of a string descriptor:
  3198.  
  3199.    Mov  AX,Offset Descriptor
  3200.    Push AX
  3201.    CALL B$STDL
  3202.  
  3203.  
  3204. B$SETM: SETMEM Function
  3205.  
  3206. B$SETM expects a long integer argument by value on the stack; if the value
  3207. is negative then that much memory is released back to DOS, and thus taken
  3208. from your BASIC program.  However, you should call B$SETM again later with
  3209. a positive value when you are finished, so the BASIC program can reclaim
  3210. that memory.  Since SETMEM is a function, B$SETM also returns the amount
  3211. of memory currently available in the DX:AX register pair.
  3212.  
  3213.  
  3214. B$SASS: String Assign
  3215.  
  3216. Where B$ASSN is capable of assigning any mix of conventional and fixed-
  3217. length strings, B$SASS works with conventional strings only.  However, it
  3218. requires only two parameters instead of six:
  3219.  
  3220.    Mov  AX,Offset Source$
  3221.    Push AX
  3222.    Mov  AX,Offset Destination$
  3223.    Push AX
  3224.    CALL B$SASS
  3225.  
  3226. Note that if the destination string is not null, its current contents are
  3227. released after assigning it from Source$.  This is the normal way that
  3228. strings are assigned, and B$ASSN also works like this.
  3229.  
  3230.  
  3231. Finding Other Routines
  3232.  
  3233. The routines just described are those that I personally have found to be
  3234. useful.  Discovering other routine names and how they are called is in
  3235. fact quite simple.  If you wanted to access, say, COMMAND$, you would write
  3236. a one-line BASIC program, and then examine the code that is generated using
  3237. Microsoft CodeView.  CodeView lets you see which and how many parameters
  3238. are being passed as well as the routine name being called, making
  3239. exploration both easy and fun.
  3240.    BASIC string functions such as COMMAND$ and ENVIRON$ return the DGROUP
  3241. address of the result string descriptor in AX, just like an assembly
  3242. language function you would write.  If you do call a built-in BASIC
  3243. function, be sure to also pass its output descriptor to B$STDL (String
  3244. Delete) when you are done with it.  Otherwise, the string space it uses
  3245. [and the temporary output descriptor] will never be released.
  3246.  
  3247.  
  3248. READING THE ARRAY DESCRIPTOR
  3249.  
  3250. Chapter 2 described the BASIC array descriptor in detail, and discussed
  3251. each of the components it contains.  Understanding how an array descriptor
  3252. works opens many opportunities to assembly language programmers, because
  3253. it lets you write routines that accept an array passed with empty
  3254. parentheses.  This was shown in the Sort routine introduced in Chapter 8,
  3255. although the techniques used there were not detailed.
  3256.    As an example of the possibilities direct access to an array descriptor
  3257. offers, I will show a subroutine that accepts a file specification, and
  3258. returns a string array filled with the names of all matching files. 
  3259. GetNames calls upon three internal BASIC routines: B$FLEN, B$RDIM, and
  3260. B$ASSN.  B$FLEN returns the length of a string, and is used here to know
  3261. how long the file specification is.  B$RDIM redimensions the passed string
  3262. array to the correct number of elements, based on the number of matching
  3263. file names that are found.  B$ASSN then assigns each element to those
  3264. names.
  3265.    This next short BASIC program shows how GetNames is set up and used.
  3266.  
  3267.  
  3268. DECLARE FUNCTION GetNames%(Array$())
  3269. REDIM Array$(1 TO 1)            'use REDIM, not DIM
  3270. Array$(1) = "*.*"               'any valid spec is okay
  3271. NumFiles% = GetNames%(Array$()) 'load all names at once
  3272.  
  3273. IF NumFiles% = 0 THEN           'were any files found?
  3274.   PRINT "No matching files."    'no, say so and end
  3275.   END
  3276. END IF
  3277.  
  3278. FOR X% = 1 TO NumFiles%         'yes, print each name
  3279.   PRINT Array$(X%)
  3280. NEXT
  3281. PRINT NumFiles; "matching files were found"
  3282.  
  3283.  
  3284. As you can see, you must establish the array initially using REDIM.  To
  3285. avoid the need for an extra parameter, the file specification is passed in
  3286. the first element of the array.  Furthermore, GetNames returns the number
  3287. of files that matched as an integer result.  If no files were encountered,
  3288. GetNames leaves the array as it was.
  3289.    When GetNames is called, the array may already contain other data, and
  3290. it can have any legal upper and lower bounds.  As long as the lowest
  3291. element number contains a valid search specification, the spec can be found
  3292. and the array will be redimensioned starting at element number one.  The
  3293. GETNAMES.BAS demonstration program on the accompanying disk adds to this
  3294. short example by sorting the names after they are read.
  3295.    A complete description of how GetNames works follows this source
  3296. listing.
  3297.  
  3298. ;GETNAMES.ASM, loads a group of file names into an array
  3299.  
  3300. .Model Medium, Basic
  3301.   Extrn B$RDIM:Proc       ;this redimensions an array
  3302.   Extrn B$ASSN:Proc       ;this assigns a string
  3303.   Extrn B$FLEN:Proc       ;this returns a string's length
  3304.  
  3305.   DTAType Struc           ;define the DOS DTA structure
  3306.     Intern  DB 21 Dup (?) ;this is used by DOS internally
  3307.     FAttr   DB ?          ;this holds the file attribute
  3308.     FTime   DW ?          ;this holds the file time
  3309.     FDate   DW ?          ;this holds the file date
  3310.     FSize   DD ?          ;this holds the file size
  3311.     FName   DB 13 Dup (?) ;this holds each file name
  3312.   DTAType Ends
  3313.  
  3314. .Data
  3315.   DTA DB Size DTAType Dup (?) ;DOS places file info here
  3316.   NumFiles   DW 0             ;how many names were read
  3317.   SpecLength DW 0             ;remembers file spec length
  3318.  
  3319. .Code
  3320.  
  3321. GetNames Proc Uses SI DI, Array:Word
  3322.  
  3323.   Local Buffer[80]:Byte  ;copy the spec here, add a zero
  3324.  
  3325. ;-- Create a local Disk Transfer Area for our own use.
  3326.   Lea  DX,DTA            ;show DOS where the new DTA goes
  3327.   Mov  AH,1Ah            ;set DTA service
  3328.   Int  21h               ;call DOS to do it
  3329.  
  3330. ;-- Read the array descriptor, get the search spec from the first element,
  3331. ;   then copy it to the stack appending a CHR$(0) byte (ASCIIZ string).
  3332.   Mov  SI,Array          ;get address of array descriptor
  3333.   Mov  BX,[SI+0Ah]       ;now BX holds adjusted offset
  3334.   Mov  AX,4              ;each element is four bytes long
  3335.   Mul  Word Ptr [SI+10h] ;multiply by first element number
  3336.   Add  BX,AX             ;BX holds first element's address
  3337.  
  3338.   Push DS                ;push source segment and address
  3339.   Push BX                ;  for call to B$ASSN later on
  3340.   Xor  AX,AX             ;tell B$ASSN source is descriptor
  3341.   Push AX                ;using a value of zero
  3342.  
  3343.   Push BX                ;pass descriptor addr to B$FLEN
  3344.   Call B$FLEN            ;this returns the length in AX
  3345.   Mov  SpecLength,AX     ;save length locally for a moment
  3346.  
  3347.   Lea  AX,Buffer         ;get the destination address
  3348.   Push SS                ;pass the segment to assign into
  3349.   Push AX                ;and then the address
  3350.   Push SpecLength        ;we're assigning a fixed length
  3351.   Call B$ASSN            ;copy the file spec to the stack
  3352.  
  3353.   Lea  BX,Buffer         ;retrieve start address of spec
  3354.   Mov  DX,BX             ;copy to DX where DOS expects it
  3355.   Add  BX,SpecLength     ;point just past end of string
  3356.   Mov  Byte Ptr [BX],0   ;and append trailing zero byte
  3357.  
  3358. ;-- Count the number of names that match the search specification.
  3359.   Mov  AH,4Eh            ;specify Find First matching name
  3360.   Mov  CX,00100111b      ;this matches any type of file
  3361.   Xor  BX,BX             ;BX counts the number of names
  3362.  
  3363. CountNames:
  3364.   Int  21h               ;see if there's a matching name
  3365.   Jc   DoneCount         ;carry set means no more names
  3366.   Inc  BX                ;otherwise, we found another one
  3367.   Mov  AH,4Fh            ;find the next matching name
  3368.   Jmp  CountNames        ;continue until there are no more
  3369.  
  3370. DoneCount:
  3371.   Mov  NumFiles,BX       ;remember how many files we found
  3372.   Or   BX,BX             ;did we fail on the first name?
  3373.   Jz   Exit              ;yes, return a count of zero
  3374.  
  3375. ;-- Now that we know how many file names there are, REDIM the string array.
  3376.   Mov  AX,1              ;specify an LBOUND of 1
  3377.   Push AX                ;pass that on to B$RDIM
  3378.   Push BX                ;and pass on the new UBOUND value
  3379.   Mov  AL,4              ;each descriptor takes four bytes
  3380.   Push AX                ;pass that on too
  3381.  
  3382.   Mov  BX,Array          ;get array descriptor again
  3383.   Mov  AX,[BX+08]        ;load the existing Features word
  3384.   Push AX                ;use that again for this call
  3385.   Push BX                ;show where array descriptor is
  3386.   Call B$RDIM            ;finally, redimension the array
  3387.  
  3388. ;-- This is the main processing loop that reads and assigns each name
  3389. ;   that is found.
  3390.   Mov  AH,4Eh            ;specify Find First matching name
  3391.   Lea  DX,Buffer         ;load address of file spec again
  3392.   Mov  BX,Array          ;get array descriptor address too
  3393.   Mov  BX,[BX+0Ah]       ;reload the adjusted offset value
  3394.   Add  BX,4              ;BX is first descriptor address
  3395.  
  3396. Do:
  3397.   Mov  CX,00100111b      ;specify any type of file again
  3398.   Int  21h               ;see if there's a matching name
  3399.   Jc   Exit              ;carry set means no more names
  3400.   Push BX                ;otherwise, save the address
  3401.  
  3402. ;-- Search for the zero that marks the end of this name.
  3403.   Mov  DI,Offset DTA.FName
  3404.   Push DS                ;in anticipation of call below
  3405.   Push DI                ;DI too while the address handy
  3406.  
  3407.   Push DS                ;ensure that ES=DS
  3408.   Pop  ES
  3409.   Mov  CL,13             ;search up to 13 characters
  3410.   Repne Scasb            ;do the search
  3411.   Mov  AL,CL             ;save the remainder in AL
  3412.  
  3413.   Mov  CL,13             ;calc number of chars to copy
  3414.   Sub  CL,AL             ;the answer is now in CX
  3415.   Dec  CX                ;don't include the zero byte
  3416.   Push CX                ;pass that on to B$ASSN
  3417.  
  3418.   Push DS                ;show where destination string is
  3419.   Push BX
  3420.   Xor  AX,AX             ;zero means B$ASSN is assigning
  3421.   Push AX                ;  to a conventional string
  3422.   Call B$ASSN            ;assign this element to the name
  3423.  
  3424.   Pop  BX                ;retrieve the descriptor address
  3425.   Add  BX,4              ;point to the next element
  3426.   Mov  AH,4Fh            ;specify Find Next matching name
  3427.   Jmp  Do                ;and keep on keepin' on
  3428.  
  3429. Exit:
  3430.   Mov  AX,NumFiles       ;assign the function output
  3431.   Ret                    ;return to BASIC
  3432.  
  3433. GetNames Endp
  3434. End
  3435.  
  3436. GetNames begins by declaring the three BASIC routines it will call as being
  3437. external.  Next the DTA structure is defined, to simplify access to the
  3438. file name address when it assigns each element in the string array.  The
  3439. only data items are the DTA itself, two working variables, and the local
  3440. stack buffer.  Since the incoming file specification needs to be converted
  3441. to an ASCIIZ string for DOS, GetNames copies that specification into Buffer
  3442. and then appends a CHR$(0) zero byte to the end.
  3443.    Once the DTA has been established, the next step is to read the file
  3444. specification passed in the first element, and copy it into local storage. 
  3445. B$FLEN is used to obtain the length of the string, so GetNames will know
  3446. how far into the buffer the zero byte will be placed.  The last preparatory
  3447. steps call B$ASSN telling it to copy from a conventional string (the array
  3448. element) to a fixed-length string (Buffer), and then store the zero byte.
  3449.    The actual body of the program is broken into two portions.  The first
  3450. simply calls DOS repeatedly to count the file names, to know how many
  3451. elements are needed.  The count is then saved in NumFiles; if none were
  3452. found GetNames exits without doing anything else.  Otherwise, the incoming
  3453. string array is redimensioned from 1 to the number of files.
  3454.    The second portion again reads each file name through DOS, but this time
  3455. the names are actually assigned to the array elements using B$ASSN.  This
  3456. time, however, B$ASSN assigns a conventional string from the fixed-length
  3457. string portion of the DTA.  Since the source is now of a fixed-length,
  3458. GetNames needs to know how long each name is.  The longest possible name
  3459. is 13 bytes long (eight for the name, a period, three for an extension, and
  3460. one more for the terminating zero byte).  Therefore, ES:DI is set to point
  3461. to the start of the DTA, AX is set to zero to search for the zero byte, and
  3462. CX is loaded with the number of characters to scan.
  3463.    Once the zero is found--and it always will be--the count that remains
  3464. in CX is subtracted from 13 to obtain the actual length of the current
  3465. name.  Because that calculation includes the unwanted CHR$(0), CX is
  3466. decremented by one.
  3467.    There is one small related trick that bears explaining.  Just before the
  3468. call to B$RDIM, AX is loaded with the number 1, to specify that as the
  3469. first element number.  This three-byte instruction sets AL to 1, and clears
  3470. AH to 0.  Three lines below that only AL is assigned, which is sufficient
  3471. because we know that AH is already zero.  Because the number being assigned
  3472. is one byte long, assigning AL requires only two bytes.
  3473.    Admittedly, the savings is small, but the affect on code readability is
  3474. minimal once you know about such tricks.  And a byte saved is always
  3475. welcome in assembly language programming.  The same trick is used when
  3476. setting CL to 13, where CH is known to be zero after assigning the file
  3477. attribute of 00100111b to all of CX.
  3478.  
  3479.  
  3480. HANDLING INTERRUPTS
  3481. ===================
  3482.  
  3483. The last programming technique I want to describe is writing an interrupt
  3484. handler you can attach to a BASIC program.  There are several applications
  3485. for this, such as tapping into the timer interrupt to display an on-screen
  3486. clock.  Instead of having to constantly print TIME$ during your INKEY$
  3487. input loops, such a routine would act as a sort of TSR, getting control at
  3488. each timer tick and displaying the time automatically.
  3489.    The example I will show here takes over the keyboard interrupt, and
  3490. disables the Ctrl-Alt-Del key sequence.  This lets you prevent rebooting
  3491. with its corresponding loss of data, should someone press those keys
  3492. inadvertently (or on purpose!).  NoReboot is called as follows:
  3493.  
  3494.    CALL NoReboot(BYVAL InstallFlag%)
  3495.  
  3496. If InstallFlag is non-zero, you are telling NoReboot to install itself and
  3497. take over the keyboard interrupt to prevent rebooting.  An argument of
  3498. zero instead unhooks the interrupt, and re-enables those keys.  Although
  3499. you could certainly modify NoReboot to use BASIC's B_ONEXIT service to
  3500. deinstall itself automatically, I have left that feature out on purpose in
  3501. the interest of clarity.  This also lets you activate NoReboot selectively
  3502. in your program, since there is no way to revoke a request to B_ONEXIT.
  3503.  
  3504. ;NOREBOOT.ASM, traps Ctrl-Alt-Del within a BASIC program
  3505.  
  3506. .Model Medium, Basic
  3507. .Code
  3508.  
  3509. NoReboot Proc Uses DS, InstallFlag:Word
  3510.  
  3511.   Cmp  InstallFlag,0     ;are they asking to install?
  3512.   Je   Deinstall         ;no, so deinstall it
  3513.  
  3514.   Cmp  CS:Old9Seg,0      ;yes, are we already installed?
  3515.   Jne  Exit              ;yes, and don't do that again!
  3516.  
  3517.   Mov  AX,3509h          ;ask DOS for current Int 9 vector
  3518.   Int  21h               ;DOS returns it in ES:BX
  3519.   Mov  CS:Old9Adr,BX     ;save it locally
  3520.   Mov  CS:Old9Seg,ES
  3521.  
  3522.   Mov  AX,2509h          ;point Int 9 to our own handler
  3523.   Mov  DX,Offset NewInt9
  3524.   Push CS                ;copy CS into DS
  3525.   Pop  DS
  3526.   Int  21h
  3527.  
  3528. Exit:
  3529.   Ret                    ;return to BASIC
  3530.  
  3531.  
  3532. ;-- Control comes here when a key is pressed or released.
  3533. NewInt9:
  3534.   Sti                    ;enable further interrupts
  3535.   Push AX                ;save the registers we're using
  3536.   Push DS
  3537.  
  3538.   In   AL,60h            ;read the keyboard scan code
  3539.   Cmp  AL,83             ;is it the Delete key?
  3540.   Jnz  Continue          ;no, continue on to the BIOS
  3541.  
  3542.   Xor  AX,AX             ;see if Alt and Ctrl are pressed
  3543.   Mov  DS,AX             ;by looking at address 0:417h
  3544.  
  3545.   Mov  AL,DS:[417h]      ;get shift status at 0000:0417h
  3546.   Test AL,8              ;is Alt key depressed?
  3547.   Jz   Continue          ;no, continue on to the BIOS
  3548.   Test AL,4              ;is Ctrl key depressed?
  3549.   Jz   Continue          ;no, continue on to the BIOS
  3550.  
  3551.   In   AL,61h            ;send an acknowledge to keyboard
  3552.   Mov  AH,AL             ;otherwise the Ctrl-Alt-Del
  3553.   Or   AL,80h            ;  keystroke will still be
  3554.   Out  61h,AL            ;  hanging around the next time
  3555.   Mov  AL,AH             ;  a program asks for a key
  3556.   Out  61h,AL
  3557.   Mov  AL,20h            ;indicate end of interrupt to the
  3558.   Out  20h,AL            ;  8259 interrupt controller chip
  3559.  
  3560.   Pop  DS                ;ignore, simply return to caller
  3561.   Pop  AX
  3562.   Iret                   ;use this special Ret when
  3563.                          ;  returning from an interrupt
  3564. Continue:
  3565.   Pop  DS                ;restore the saved registers
  3566.   Pop  AX
  3567.   Jmp  DWord Ptr CS:Old9Adr   ;continue on to the BIOS by
  3568.                               ;  jumping to the address
  3569.                               ;  that was saved during
  3570.                               ;  initialization
  3571. DeInstall:
  3572.   Mov  AX,2509h          ;restore original Int 9 handler
  3573.   Mov  DX,CS:Old9Adr     ;from segment and address saved
  3574.   Mov  DS,CS:Old9Seg     ;  earlier
  3575.   Int  21h               ;DOS does this for us
  3576.   Mov  CS:Old9Seg,0      ;clear this as an installed flag
  3577.   Jmp  Short Exit        ;and then exit back to BASIC
  3578.  
  3579. NoReboot Endp
  3580.  
  3581.   Old9Adr   DW 0         ;remembers original Int 9 address
  3582.   Old9Seg   DW 0         ;these must be stored in the code
  3583.                          ; segment because DS is undefined
  3584.                          ; when NewInt9 receives control
  3585. End
  3586.  
  3587. The first thing NoReboot does is look to see if the caller is installing
  3588. or deinstalling.  If installation is requested, the saved Interrupt 9
  3589. segment is checked, to be sure that it holds the initial value of zero. 
  3590. It is important to prevent multiple installations, because installing saves
  3591. the current interrupt handler's address.  If NoReboot installed itself
  3592. twice, it would save its own address on top of the original BIOS handler's
  3593. saved address.  And once that address is lost, it is impossible to restore
  3594. it again later.
  3595.    Assuming it is safe to be installed, the next step is to ask DOS for the
  3596. current interrupt handler's address using service 35h.  This service
  3597. expects the service number in AH, and the interrupt number in AL.  To save
  3598. a byte, both values are loaded at once.  Service 35h returns the segment
  3599. and address in ES:BX, and these are saved in the code segment.  Because the
  3600. original address will be called from within the interrupt handler, CS is
  3601. the only register whose contents are known.  Accessing data in DGROUP is
  3602. more difficult, because an interrupt can occur at any time, and DS will
  3603. likely not be holding the correct segment.  [That is, execution could be
  3604. at any point in the program when Ctrl-Alt-Del is pressed, including within
  3605. a routine that has changed DS.  So when NoReboot receives control it can't
  3606. be certain that DS holds the segment for .Data variables it has defined.]
  3607.    Once the original interrupt handler address has been saved, NoReboot
  3608. calls DOS again, but this time to assign the segment and address of its
  3609. replacement handler in the interrupt vector table.  It is easy to access
  3610. the interrupt vector table directly using Mov instructions, but it is even
  3611. easier to have DOS do that.
  3612.    Finally, NoReboot returns to the calling BASIC program, and all
  3613. subsequent key presses are now routed to the NewInt9 procedure.
  3614.    NewInt9 must perform a few tricks, partly because it is handling a
  3615. hardware interrupt.  All interrupt handlers begin with the instruction Sti,
  3616. which tells the 8088 to allow further interrupts to occur and be processed. 
  3617. Next, the two registers being used are saved on the stack, so they can be
  3618. restored again later.  Because a keyboard interrupt can occur at any time
  3619. interrupting the process that is currently running, it is imperative that
  3620. you not alter any aspect of the 8088's current state.  This includes the
  3621. settings of the Flags register as well.  However, the Flags register is
  3622. saved automatically by the 8088 as part of its handling of interrupts, so
  3623. the flags don't have to be saved or restored manually using Pushf and Popf.
  3624.    The next sequence of instructions reads the key that was pressed from
  3625. the keyboard's I/O port (60h), and compares that to the scan code for the
  3626. Del key.  If any other key was pressed, NoReboot jumps to the original
  3627. keyboard handler in the ROM BIOS.  Otherwise, it examines low memory to see
  3628. if both the Ctrl and Alt keys are also currently pressed.  Unless all three
  3629. conditions are met, control passes on to the BIOS.  But if Ctrl-Alt-Del is
  3630. pressed, NoReboot handles the keystroke entirely on its own and ignores it. 
  3631. In that case DS and AX are restored, and NoReboot exits back to the
  3632. underlying program.
  3633.    Notice the special form of return command, Iret (Interrupt Return). 
  3634. Like a conventional far return, Iret pops the address and segment to return
  3635. to from the stack, but it also pops the Flags register that was stored
  3636. there by the 8088 automatically.
  3637.    The final section of code restores the original interrupt vector, and
  3638. clears the Old9Seg variable to zero.  This lets NoReboot know that it is
  3639. not installed, in case you call it again later.
  3640.    This same technique can be applied to handle other interrupt services,
  3641. and I encourage you to experiment on your own.  You could, for example,
  3642. write a routine that takes over the communications interrupt, and displays
  3643. a flashing box in a corner of the screen whenever characters are received. 
  3644. Likewise, you could modify this routine to create an on-screen display of
  3645. the Caps Lock and Num Lock state.  Each time one of those keys is pressed
  3646. you would either print or clear a status message.
  3647.  
  3648.  
  3649. DEBUGGING WITH CODEVIEW
  3650. =======================
  3651.  
  3652. As useful as CodeView can be for a purely BASIC program, it is even more
  3653. necessary when writing in assembly language.  CodeView lets you step
  3654. through the code that BASIC generates to set up and call your subroutine,
  3655. and then step through the routine a line at a time.  Being able to watch
  3656. your program as it executes helps you to quickly zero in on any problems. 
  3657. Further, CodeView shows you the current CPU register contents, as well as
  3658. the value of memory locations about to be read from or written to.
  3659.    To debug an assembly language subroutine with CodeView, you must first
  3660. assemble it using the /Zi option switch:
  3661.  
  3662.    masm routine /zi;
  3663.  
  3664. Then you link the routine to your BASIC program using the /Co option.  Of
  3665. course, the BASIC program must also have been compiled using /Zi:
  3666.  
  3667.    bc program /o /zi;
  3668.    link program routine /co;
  3669.  
  3670. Finally, you start CodeView specifying the name of the BASIC program:
  3671.  
  3672.    cv program
  3673.  
  3674. Once the BASIC source code is showing on the screen you can step and trace
  3675. through it as described in Chapter 4.  As with BASIC subprograms and
  3676. functions, to step into an assembler routine you press F8 at the CALL
  3677. statement.  If the routine is designed as a function you instead press F8
  3678. at the line in which the function is referenced.
  3679.    Once CodeView has traced into the routine, you can press F3 to view the
  3680. source code only, the assembly code only, or both intermixed.  I usually
  3681. prefer to view only my original source, but that hides the data memory
  3682. addresses that MASM and LINK assigned.  Usually you will not need to know
  3683. those addresses, but there are times when this can be helpful.  For
  3684. example, when a program is not working correctly, the bug could be caused
  3685. by a different portion of the program overwriting the named variables.
  3686.    Besides the F3 key, you can also use F4 and F7, and these have the same
  3687. meaning as the same keys when used in the BASIC editor.  Indeed, debugging
  3688. an assembly language subroutine is quite similar to debugging a BASIC
  3689. program as far as which keys are used.
  3690.  
  3691.  
  3692. MASM 6.0 ENHANCEMENTS
  3693. =====================
  3694.  
  3695. All of the discussions in this chapter have focused on using MASM version
  3696. 5.1.  However, Microsoft's more recent version 6.0 introduces a number of
  3697. significant changes and new features.  Perhaps the most useful new feature
  3698. in this release is the greatly improved documentation.  The manuals that
  3699. came with past versions of MASM were very dry, containing reams of facts
  3700. but no practical advice or guidance.  The new documentation include both
  3701. facts and programming tips, and this addition is welcome indeed.
  3702.    If you already have existing assembly language source code, you may have
  3703. to change it to accommodate the new MASM 6.0 conventions.  In particular,
  3704. MASM's handling of data structures has changed substantially, and in many
  3705. cases code that used to work correctly no longer does.  However, you can
  3706. optionally use the /Zm command line switch, to tell MASM 6.0 to behave like
  3707. the earlier 5.1 version.
  3708.    A new MASM.EXE program launcher is also included to offer a similar
  3709. capability.  Where older versions of MASM were named MASM.EXE, the new
  3710. program is called ML.EXE.  The MASM.EXE that now comes with MASM 6.0 simply
  3711. passes the /Zm option on to ML, along with some other option switches that
  3712. are needed to tell ML to mimic the older assembler's behavior.
  3713.  
  3714.  
  3715. IMPROVED ASSEMBLY OPTIMIZATIONS
  3716.  
  3717. Before MASM 6.0, a conditional jump was limited to a distance no greater
  3718. than 128 bytes earlier or 127 bytes farther ahead in the code.  When there
  3719. was no way to restructure your code to accommodate this inherent 8088
  3720. limitation, you had to use a conditional jump around another unconditional
  3721. jump like this:
  3722.  
  3723.    ;if AX < 12 go to FarLabel
  3724.      Cmp  AX,12              ;compare AX to 12
  3725.      Jnl  NearLabel          ;jump if not less over far jump
  3726.      Jmp  FarLabel           ;perform the far jump
  3727.    NearLabel:
  3728.       .                      ;program continues
  3729.       .
  3730.       .                      ;this label is more than
  3731.    FarLabel:                 ;  127 bytes past Jnl
  3732.  
  3733. MASM 6.0 avoids this limitation and lets you use Jl to the far label
  3734. directly, although it really just replaces your use of Jl with code
  3735. equivalent to that shown above.
  3736.    Another, similar optimization affects unconditional jumps.  As I
  3737. mentioned earlier, each time MASM 5.1 encounters a label in your source
  3738. code, it remembers its address in the resultant object code.  Then if you
  3739. jump backwards to that label later, MASM knows if it can use the shorter
  3740. two-byte form of the Jmp instruction.  But a forward jump to a near label
  3741. requires you to explicitly state Jmp Short to obtain this code savings,
  3742. since MASM 5.1 does not yet know the target label's address.  Without
  3743. Short, MASM 5.1 uses a long jump on a trial basis.  If the jump turns out
  3744. to be within the near range MASM goes back and patches the code to a short
  3745. jump followed by a byte-wasting Nop (No Operation) instruction.
  3746.    MASM 6.0 avoids this problem by processing your source file in multiple
  3747. passes.  That is, MASM reads your code and assembles what it can, using far
  3748. jumps when the target address has not yet been encountered.  Then it
  3749. processes that intermediate code again modifying its earlier output as
  3750. appropriate.  If a three-byte jump can be replaced with the two-byte
  3751. version, MASM 6.0 rewrites the code sliding subsequent instructions back
  3752. a byte.  MASM 6.0 is called an *n-pass assembler*, because as many passes
  3753. as needed are performed until the code is as small as possible.
  3754.  
  3755.  
  3756. NEW SIMPLIFIED DIRECTIVES
  3757.  
  3758. Besides the improved optimizing, MASM 6.0 offers several features borrowed
  3759. from high-level languages.  These include .IF, .ELSE, and .ELSEIF; .WHILE
  3760. and .ENDW; and .REPEAT and .UNTIL.  Unfortunately, these new constructs are
  3761. modeled after the C language, and provide little if any clarification to
  3762. BASIC programmers.  For example, you can now write code such as this:
  3763.  
  3764.    .IF (AL < "0") || (AL > "9")
  3765.  
  3766. which is equivalent to this BASIC statement:
  3767.  
  3768.    IF AL < ASC("0") OR AL > ASC("9")
  3769.  
  3770. Even worse, the MASM manual does not document each directive showing
  3771. precisely what it does to your code.
  3772.    Like C, BASIC's AND is replaced with a double ampersand (&&), testing
  3773. for equality uses a double equals sign (==), and NOT is replaced with an
  3774. exclamation point (!).  Therefore, you could write assembly language source
  3775. statements like these next two examples:
  3776.  
  3777.    .IF (AX != 14) && (BX < 10) ;IF AX <> 14 AND BX < 10 THEN
  3778.      Mov  AX,SomeVar           ;divide SomeVar by CX
  3779.      Cwd
  3780.      Div  CX
  3781.      Mov  SomeVar,AX
  3782.    .ENDIF
  3783.  
  3784.  
  3785.    .REPEAT
  3786.      Mov  AH,1                 ;ask for a keyboard character
  3787.      Int  21h                  ;through DOS
  3788.    .UNTIL (AL == 13)           ;loop until they press Enter
  3789.  
  3790. PROTO and INVOKE are two other new simplified directives, and it's hard
  3791. for me to recommend using them for similar reasons.  PROTO mimics C's
  3792. function prototype capability, and lets you define a called procedure and
  3793. its arguments.  INVOKE then calls that routine passing the arguments you
  3794. give it.  To define a procedure called, say, MyProc, you would use PROTO
  3795. like this:
  3796.  
  3797.    MyProc PROTO Var1:Word, Var2:Word, Var3:DWord
  3798.  
  3799. Then to call MyProc you use INVOKE as follows:
  3800.  
  3801.    INVOKE MyProc, BX, 100, LongVar
  3802.  
  3803. Thus, PROTO and INVOKE are very similar to DECLARE SUB and CALL in BASIC. 
  3804. The problem is that you have no way to know what code MASM generates for
  3805. this command unless you create a sample program, assemble it, and examine
  3806. the result using CodeView.  In particular, how does the value 100 used here
  3807. get onto the stack?  As it turns out, assembling the preceding INVOKE
  3808. command results in the following code:
  3809.  
  3810.    Push BX
  3811.    Mov  AX,100
  3812.    Push AX
  3813.    Push Word Ptr [LongVar+2]
  3814.    Push Word Ptr [LongVar]
  3815.  
  3816. As you can see, even if AX is holding an important value, its contents are
  3817. destroyed when MASM assigns the value 100 prior to placing it on the stack. 
  3818. While I applaud Microsoft's attempts to make assembly language easier to
  3819. use, such behavior can and will introduce subtle bugs.  These bugs can be
  3820. even harder to track down than usual, because you did not make the coding
  3821. error, the assembler did!  Since the whole point of programming in assembly
  3822. language is to control fully what the CPU is doing, such hidden behavior
  3823. can have disastrous effects.
  3824.    One new feature that I do find useful, however, is the ability to
  3825. continue a line with a trailing comma.  Often, a single source statement
  3826. will extend into the comments column, spoiling the appearance of your
  3827. listing.  You can now avoid this by placing a comma in the middle of a
  3828. logical line, and then continuing the remainder of the statement on the
  3829. next line.
  3830.    Another very useful feature is MASM 6's ability to accept wild cards on
  3831. the command line.  For example, you can assemble all of the files in the
  3832. current directory using the command masm *.asm;.
  3833.  
  3834.  
  3835. TRICKS OF THE TRADE
  3836. ===================
  3837.  
  3838. The final topic I want to present is a variety of assembly language
  3839. programming short cuts and other techniques I have developed over the
  3840. years.  In preceding sections you saw how Xor or Sub can be used to clear
  3841. a register, using less code than Mov.  And if you know that the high-byte
  3842. portion of a register or memory variable is already zero, you can save a
  3843. byte by assigning only the lower byte.  And to clear both AX and DX you can
  3844. use Xor with AX, and then Cwd to extend the zero into DX using only one
  3845. additional byte.  As you might imagine, there are many other ways to be
  3846. clever in assembly language.
  3847.  
  3848.  
  3849. MINIMIZE CODE TO ACCESS PARAMETERS
  3850.  
  3851. When parameters are accessed within an assembly language subroutine, the
  3852. usual way to get at them is through BP.  Even when you use MASM's
  3853. simplified directives, code to push BP, assign it from SP, and then
  3854. reference the address on the stack is added to your program.  In that case,
  3855. the steps are simply hidden from you.  Because BASIC (and indeed, every
  3856. high-level language) requires you to preserve BP, one byte each is needed
  3857. for the Push and Pop instructions.
  3858.    You can eliminate that overhead by taking advantage of the fact that the
  3859. stack is always kept in DGROUP, and that SS and DS are equal.  The trick
  3860. is to use BX as a stack reference, because it doesn't need to be preserved. 
  3861. Unfortunately, this precludes using the simplified methods for parameter
  3862. access.  But when speed or code size are paramount or you have many
  3863. routines, stack addressing via BX affords a real savings.  Here's how you
  3864. will design the routine, using an example that accesses an incoming string:
  3865.  
  3866.    GetString Proc       ;one parameter, not shown
  3867.  
  3868.      Mov  BX,SP         ;address the stack manually using BX
  3869.      Mov  BX,[BX+04]    ;get the address for the string
  3870.      Mov  CX,[BX]       ;get the length of the string
  3871.      Jcxz Exit          ;quit if the string is null
  3872.      Mov  BX,[BX+02]    ;get address of first character
  3873.  
  3874.    Exit:
  3875.      Retf 2             ;specify far return with 2 bytes
  3876.  
  3877.    GetString Endp
  3878.    End
  3879.  
  3880. Because BP has not been pushed onto the stack, the incoming string
  3881. descriptor address is at [BX+4] rather than [BX+6].  Other than that, the
  3882. remainder of the routine proceeds as usual.
  3883.  
  3884.  
  3885. BYTE SAVERS
  3886.  
  3887. Another useful trick lets you save a byte when adding two to a variable. 
  3888. As you know, Inc and Dec when used with a register are always better than
  3889. Add and Sub, because they are one-byte instructions.  Therefore, two Inc
  3890. or Dec commands in a row are still better than Add AX,2 which requires
  3891. three bytes.  However, you must never do this with SP.  The stack pointer
  3892. must always hold an even number, and it is possible that an interrupt could
  3893. come along after the first Inc or Dec, but before the second has executed. 
  3894. Which brings up a related byte saver.
  3895.    If you need only a single word of local stack storage, don't use Sub
  3896. SP,2 to allocate the space and Add SP,2 later to clear it.  Instead, simply
  3897. use Push AX, or Push with any other register.  Likewise, just before
  3898. returning to BASIC, pop any register that doesn't return information, such
  3899. as CX or BX.
  3900.  
  3901.  
  3902. Rep Always Clears CX
  3903.  
  3904. Another trick you can take advantage of is that CX is often zero after a
  3905. repeating string command that uses Rep.  Zero is a common value in assembly
  3906. language programming, and you can usually save a byte by using a register
  3907. instead of a constant zero.  In particular, if you are copying a file name
  3908. to a buffer and adding a CHR$(0) to the end, you can use code like this:
  3909.     .
  3910.     .                   ;set up DS:SI and ES:DI here
  3911.    Mov  CX,NumBytes
  3912.    Rep  Movsb
  3913.    Mov  [DI],CL         ;tack a zero byte onto the end
  3914.     .
  3915.     .
  3916.  
  3917. This trick is made even more valuable by the fact that DI is left pointing
  3918. at the byte just past the data that was just copied.  Of course, CX is not
  3919. necessarily zero after Repe or Repne, because those forms of Rep can
  3920. terminate before CX is exhausted.
  3921.  
  3922.  
  3923. Use AX Where Possible
  3924.  
  3925. Another little-known fact is that memory operations that use AX are one
  3926. byte smaller than equivalent operations on any other register.  That is,
  3927. Mov BX,KeyCode results in four bytes of code, whereas Mov AX,KeyCode
  3928. creates only three.  I often use the DOS DEBUG program for quick tests,
  3929. just to see which sequence of instructions results in less code.  Since
  3930. DEBUG does not let you specify a variable name, use [100] or any other
  3931. address instead:
  3932.  
  3933.    -a 100
  3934.    -####:0100 Mov AX,[100]
  3935.    -####:0103 Mov BX,[100]
  3936.    -####:0107 <press Enter to stop assembling>
  3937.    -u 100,106
  3938.     ####:0100 A10001      MOV   AX,[0100]
  3939.     ####:0103 8B1E0001    MOV   BX,[0100]
  3940.    -q
  3941.  
  3942. This sample session tells DEBUG to begin assembling at address 100 (the
  3943. default for .COM files), and then assemble the two instructions shown. 
  3944. When you are done press Enter at the dash prompt, and then unassemble the
  3945. results and quit.  As you can see, using AX creates one less byte of code.
  3946.  
  3947.  
  3948. Multiplying and Dividing By a Power of 2
  3949.  
  3950. Because of the way binary numbers are organized, shifting the bits left
  3951. or right can provide a very fast way to multiply or divide by a power of
  3952. two.  And because the bit shifting commands can be used with all but the
  3953. segment registers, this can also save you from having to copy the data to
  3954. AX or DX:AX first.  To divide a register by two simply shift the bits right
  3955. one position:
  3956.  
  3957.    Shr CX,1
  3958.  
  3959. And to multiply by two shift them left:
  3960.  
  3961.    Shl SI,1
  3962.  
  3963. If you need to multiply or divide by four, eight, sixteen, and so forth,
  3964. the shift count must first be placed into the CL register:
  3965.  
  3966.    Mov CL,5       ;prepare to divide BP by 32
  3967.    Shr BP,CL
  3968.  
  3969. On 80186 and later processors you can specify a shift count directly. 
  3970. Unfortunately, this doesn't work with an 8088, so CL must be used.  Still,
  3971. multiplying and dividing are extremely slow instructions on an 8088, so the
  3972. added setup will be more than offset if speed is the primary factor.
  3973.  
  3974.  
  3975. Low Memory is at Segment Zero
  3976.  
  3977. Another useful byte saver is to treat the BIOS data area in low memory as
  3978. being at segment zero, instead of the more commonly used segment 40h.  By
  3979. convention, the BIOS data area is said to reside at segment 40h, even
  3980. though a number of segment/address pairs can be used to access that data. 
  3981. I mentioned this briefly in Chapter 11, in the discussions about using
  3982. BASIC's CALL Interrupt.  Since Xor or Sub can be used to clear a register
  3983. to zero with one byte less code than assigning it a value of 40, I use this
  3984. technique frequently:
  3985.  
  3986.    This example generates 9 bytes:
  3987.         Xor  AX,AX
  3988.         Mov  DS,AX
  3989.         Test Byte Ptr [417h],8  ;see if the Alt key is depressed
  3990.  
  3991.    And this example creates 10 bytes:
  3992.         Mov  AX,40h
  3993.         Mov  DS,AX
  3994.         Test Byte Ptr [17h],8
  3995.  
  3996.  
  3997. Scanning An ASCIIZ String
  3998.  
  3999. Because ASCIIZ strings are used in programs that access DOS services,
  4000. searching those strings to find the end is a common operation.  For
  4001. example, the GetNames function does this to determine the length of each
  4002. file name before assigning it to elements in the incoming string array. 
  4003. In that routine CX is assigned to 13, which is the maximum length a file
  4004. name can be.  Since CX is decremented for each character that is examined,
  4005. the length is calculated by subtracting CX from 13, which requires an extra
  4006. register.
  4007.    As long as you are certain that a zero byte is present, you can use a
  4008. clever trick to determine directly the number of bytes that were searched. 
  4009. Instead of loading CX with the maximum number of bytes to scan, assign it
  4010. to -1.  As each character is searched CX is decremented, which results in
  4011. a negative version of the number of bytes.  Then the NOT instruction can
  4012. be used to revert that to a positive number:
  4013.  
  4014.    Mov  ES,Segment     ;point ES:DI to the start of the data
  4015.    Mov  DI,Address
  4016.    Cld                 ;ensure that scanning is forward
  4017.    Mov  CX,-1          ;set CX to -1
  4018.    Mov  AL,0           ;search for a zero byte
  4019.  
  4020.    Repne Scasb         ;scan the string
  4021.    Not  CX             ;convert to a positive number
  4022.    Dec  CX             ;don't include the zero byte itself
  4023.    Mov  AX,CX          ;now AX holds the length of the string
  4024.  
  4025. As you learned in Chapter 2, BASIC's NOT instruction flips all of the bits,
  4026. converting ones to zeros and vice versa.  The assembly language version
  4027. works the same way, and can be used with registers or memory locations.
  4028.  
  4029.  
  4030. CYCLE SAVERS
  4031.  
  4032. Besides savings bytes when possible, most assembly language programmers
  4033. also like to save clock cycles.  Every assembler instruction requires a
  4034. certain amount of CPU timing cycles to execute, although there are other
  4035. factors that also affect the actual throughput of a given piece of code. 
  4036. But instructions with the fewest number of clock cycles as published by
  4037. Intel are always faster than those that require more cycles.
  4038.  
  4039.  
  4040. Move and Store Words Instead of Bytes
  4041.  
  4042. One very effective speed enhancement is to copy and store words when
  4043. possible, instead of bytes.  On 80286 and later processors, words are moved
  4044. and stored as quickly as bytes.  Therefore, moving 50 words is much faster
  4045. than moving 100 bytes.  If you know ahead of time how many bytes are going
  4046. to be processed and that the number is even, you can simply load CX with
  4047. half the value, and use Rep Movsw or Rep Stosw instead of Rep Movsb or Rep
  4048. Stosb.  [This trick can be used even if the program runs on an 8088, but
  4049. the speedup only occurs with 80286 and later CPUs.]  With only a little
  4050. added code you can also use this technique to determine at runtime if an
  4051. odd byte needs to be processed.  Here's one way to do that:
  4052.  
  4053.    Shr  CX,1     ;divide CX by 2
  4054.    Rep  Movsw    ;copy the words
  4055.    Jnc  Done     ;the Carry Flag is clear
  4056.    Movsb         ;copy the odd byte
  4057.    Done:
  4058.     .            ;program continues
  4059.     .
  4060.  
  4061. First, CX is divided by 2, and the odd bit, if there was one, is stored
  4062. by the CPU in the Carry Flag.  Then the data words are copied to their
  4063. destination.  Finally, the Carry flag is tested and the program either
  4064. copies a single additional byte or skips over that command.
  4065.  
  4066.  
  4067. A Jump Not Taken is Faster Than One That is
  4068.  
  4069. And this brings us to yet another cycle saver.  In some cases the Jnc will
  4070. be executed, and in others it will not.  And in most programs, the chances
  4071. of either happening are about fifty-fifty.  But if you know ahead of time
  4072. that a particular action will happen less often than another, you can take
  4073. advantage of another 8088 fact: A jump not taken is always faster than one
  4074. that is taken.
  4075.    Each time the 8088 jumps to a new location or calls a procedure, it
  4076. discards its *pre-fetch queue*.  The pre-fetch queue is a small area of
  4077. memory on the CPU itself that holds the next few instructions to be
  4078. executed.  In many cases, the 8088 can do several things at once.  So while
  4079. it is adding or subtracting numbers, it simultaneously fetches instruction
  4080. bytes from your code, in anticipation of what it will do next.  This lets
  4081. the CPU act on the subsequent instructions very quickly, because they are
  4082. already in its own local on-chip memory.  Just as data in registers can be
  4083. accessed faster than data that must be read from memory, so too can
  4084. instructions that are already in the CPU.
  4085.    But when execution branches to a new location, any bytes present in the
  4086. pre-fetch queue are obsolete.  Therefore, the 8088 must read the new bytes
  4087. at the new location, which takes additional time.  If you have a routine
  4088. that makes a test repeatedly within a loop you should change the logic as
  4089. necessary, to branch on the less likely situation.  That is, instead of Jne
  4090. you might use Je, or vice versa.
  4091.  
  4092.  
  4093. MISCELLANEOUS TECHNIQUES
  4094.  
  4095. One very powerful technique you will surely find useful is self-modifying
  4096. code.  As its name implies, self-modifying code actually writes new
  4097. instructions into its own code segment, and this is useful in a variety
  4098. of situations.  For example, if you are writing a routine that accepts a
  4099. variable number of parameters this lets you patch the Ret instruction to
  4100. be Ret 2, Ret 4, and so forth.
  4101.    One warning, however, is related to the pre-fetch queue.  If a byte or
  4102. word has already been read into the CPU, changing it in the code segment
  4103. has no effect.  Worse, there is no way to know for certain which bytes will
  4104. have already been read, because the size of the pre-fetch queue has grown
  4105. with each new CPU from Intel.  For example, only four bytes are allocated
  4106. for a pre-fetch queue on an 8088, but the 80386 uses 16 bytes.
  4107.    In general, if the code you are patching is located at least a few dozen
  4108. bytes farther in the program, you should be safe.  Such self-modifying code
  4109. was used in the SORT.ASM routine shown in Chapter 8, to let the same code
  4110. sort either forward or backward.  There, the bytes that represent Jae and
  4111. Jbe were assigned to AL and AH, and the code was patched based on the
  4112. incoming sort direction.  Since the patching takes place a hundred or so
  4113. bytes earlier in the program, it is unlikely that this routine will fail
  4114. with future processors.
  4115.  
  4116.  
  4117. Static-Free CGA Text Display
  4118.  
  4119. The final technique you will find useful is writing to CGA text mode video
  4120. memory without creating a disturbance.  When IBM designed the original CGA
  4121. adapter they skimped on the design, using circuitry that shares a single
  4122. address line for both the 8088 CPU and the video hardware that updates the
  4123. screen.  Even when a program is not reading from or writing to display
  4124. memory, that memory is still read periodically by the display adapter and
  4125. sent to the monitor.  Therefore, accessing that memory directly from an
  4126. assembly language routine creates a disturbing burst of static that is
  4127. visible on the monitor.  This is caused by the conflict of the CPU and the
  4128. video adapter accessing the same video memory addresses at the same time.
  4129.    Newer CGA adapters employ a dual-port design that arbitrates
  4130. simultaneous read and write requests, thereby eliminating this problem. 
  4131. And, of course, EGA and VGA adapters are much more sophisticated than the
  4132. CGA, and fortunately also more common these days.  However, you can avoid
  4133. the screen disturbance on older CGA adapters by synchronizing your reading
  4134. and writing with the horizontal retrace timing.
  4135.    As you undoubtedly know, the image on a CRT is drawn by scanning a
  4136. single dot horizontally across each successive row.  This happens so
  4137. quickly that the eye perceives the moving dot as an entire image.  After
  4138. each row is drawn, the dot is turned off, quickly placed at the start of
  4139. the next row below, and then turned on again.  By writing to the screen
  4140. only while the dot is turned off you can hide the memory conflicts that
  4141. cause static.
  4142.    The short code fragment below shows how to synchronize video writing
  4143. with the CGA's horizontal retrace.  In a windowing routine that also needs
  4144. to read video memory, you would use the same technique just before each
  4145. byte or word is read.
  4146.  
  4147.     .
  4148.     .
  4149.    Mov  SI,Descriptor  ;get the incoming descriptor address
  4150.    Mov  CX,[SI]        ;the string's length goes in CX
  4151.    Mov  SI,[SI+2]      ;and the address of the data in SI
  4152.  
  4153.    Mov  AX,&HB800      ;load ES with the CGA video segment
  4154.    Mov  ES,AX          ;through AX
  4155.    Xor  DI,DI          ;point DI to the upper left corner
  4156.  
  4157.    Mov  AH,Color       ;load color parameter (passed BYVAL)
  4158.    Jcxz Done           ;don't try to print a null string!
  4159.  
  4160. No_Retrace:
  4161.    In   AL,DX          ;get the video status byte
  4162.    Test AL,1           ;test the horizontal retrace bit
  4163.    Jnz  No_Retrace     ;if doing retrace, wait until done
  4164.    Cli                 ;disable interrupts until we're done
  4165.  
  4166. Retrace:
  4167.    In   AL,DX          ;get the status byte again
  4168.    Test AL,1           ;are we currently doing a retrace?
  4169.    Jz   Retrace        ;no, wait until we are
  4170.    Lodsb               ;load the current character
  4171.    Stosw               ;store the character and attribute
  4172.  
  4173.    Sti                 ;re-enable interrupts
  4174.    Loop No_Retrace     ;loop until the string is printed
  4175.  
  4176.    Done:
  4177.     .                  ;program continues or exits here
  4178.     .
  4179.  
  4180. The current horizontal retrace status can be read using the In instruction,
  4181. and then masking off all but the lowest bit.  To protect against the case
  4182. where the print loop is entered just as the retrace is about to end, this
  4183. routine waits until a new period has just begun.  This is not unlike the
  4184. empty loop used in the benchmark examples in Chapter 9, that waited for a
  4185. new system clock cycle to begin.
  4186.  
  4187.  
  4188. SUMMARY
  4189. =======
  4190.  
  4191. In this final chapter you have learned what assembly language programming
  4192. is all about, and how it can help you as a BASIC programmer.  There is no
  4193. doubt that using assembly language is more tedious than BASIC, but the
  4194. overall methods and code structures are similar.
  4195.    You learned about the 8088's registers, and why operations that use them
  4196. are faster than similar operations on memory variables.  The string
  4197. instructions are particularly useful, because they are very small and do
  4198. several things at once.  Coupled with the Rep prefix these commands can
  4199. replace many separate Mov and Inc and Cmp statements.  You also learned how
  4200. to perform simple calculations in assembly language, and an example showed
  4201. how to translate simple BASIC integer and floating point expressions.
  4202.    This chapter explained how the stack operates, and how procedures are
  4203. designed to accept passed parameters.  The new simplified directives
  4204. introduced with MASM 5.1 eliminate the need to define segments and figure
  4205. parameter stack displacements in your routines.  This chapter also
  4206. explained how to call DOS and BIOS interrupts from assembly language.
  4207.    You learned how to access every kind of data a BASIC program can pass
  4208. to a routine, including near and far strings, integers, and even floating
  4209. point values.  The section that described arrays showed how to access both
  4210. near and far data, and even huge arrays that span multiple segments.
  4211.    Besides conventional called procedures, you also learned how to create
  4212. functions that can return any type of data.  Several innovative techniques
  4213. were presented, including a method for creating a single procedure that can
  4214. work with both near and far strings, and even with different versions of
  4215. the BASIC compiler.  Equally innovative are the methods that show how to
  4216. write floating point instructions and tie them into BASIC's software
  4217. emulator.  And if you are not certain how to code a particular floating
  4218. point instruction, you can create a short BASIC program and then examine
  4219. its code using CodeView.
  4220.    This chapter explained many of MASM's features, such as initialized
  4221. data, conditional assembly, and defining structures and macros.  In
  4222. particular, macros can greatly simplify coding redundant instructions and
  4223. data definitions.  Furthermore, MASM can calculate data addresses and
  4224. lengths automatically, reducing your work when the data must be changed
  4225. later on.
  4226.    Because so many different data items all compete for the same 64K near
  4227. memory segment, it is often desireable to store working variables on the
  4228. system stack.  Likewise, when large amounts of data are involved, variables
  4229. and tables can be stored in the code segment.  Both of these techniques
  4230. were described in depth, and accompanying examples showed how to do this
  4231. in context.
  4232.    Several of BASIC's most useful internal variables and procedures were
  4233. described, showing their public names and parameter requirements.  The
  4234. GetNames function brought all of this information together, showing how to
  4235. read an array descriptor, redimension a string array, and assign individual
  4236. elements--all using code that works identically with both near and far
  4237. strings.
  4238.    You also learned how to write an interrupt handler that can be installed
  4239. and deinstalled from within a BASIC program.  The example showed how to
  4240. take over the keyboard interrupt; however, the same technique can be
  4241. applied to nearly any other hardware or software interrupt as well.
  4242.    Finally, this chapter described many useful tricks and techniques that
  4243. help to reduce the size of your assembly language routines, and also make
  4244. them faster.  Many operations that use the AX register result in less code
  4245. than the same operations using other registers.  And when moving or storing
  4246. contiguous data, accessing the data as words instead of bytes can sometimes
  4247. yield a nearly two-fold speed improvement.  When in doubt about which of
  4248. several sequences of code is smaller, you can use the DOS DEBUG utility to
  4249. quickly determine that.
  4250.